[go: nahoru, domu]

Merge "Fix performance regression in new layers implementation" into androidx-main
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 1387c96..5cc8a4c 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -16,10 +16,10 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.3.0")
-    api(projectOrArtifact(":core:core"))
+    api("androidx.core:core:1.13.0")
 
     // Required to make activity 1.5.0-rc01 dependencies resolve.
-    implementation("androidx.core:core-ktx:1.8.0")
+    implementation("androidx.core:core-ktx:1.13.0")
     implementation(libs.kotlinStdlib)
 
     implementation("androidx.emoji2:emoji2:1.3.0")
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index 7d195cc..4179410 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -242,7 +242,7 @@
         // - group by class and method (ignoring flag) and for each group keep only the first value
         // - apply the filters
         // - sort with comparator
-        val filteredProfileRules = profileRules
+        val filteredBaselineProfileRules = profileRules
             .sorted()
             .asSequence()
             .mapNotNull { ProfileRule.parse(it) }
@@ -265,10 +265,11 @@
                 return@filter !rules.any { r -> r.isInclude() }
             }
             .sortedWith(ProfileRule.comparator)
-            .map { it.underlying }
 
         // Check if the filters filtered out all the rules.
-        if (profileRules.isNotEmpty() && filteredProfileRules.isEmpty() && rules.isNotEmpty()) {
+        if (profileRules.isNotEmpty() &&
+            filteredBaselineProfileRules.isEmpty() &&
+            rules.isNotEmpty()) {
             throw GradleException(
                 """
                 The baseline profile consumer plugin is configured with filters that exclude all
@@ -278,28 +279,11 @@
             )
         }
 
-        baselineProfileDir
-            .file(BASELINE_PROFILE_FILENAME)
-            .get()
-            .asFile
-            .apply {
-                delete()
-                if (filteredProfileRules.isNotEmpty()) {
-                    writeText(filteredProfileRules.joinToString(System.lineSeparator()))
-                    if (lastTask.get()) {
-                        // This log should not be suppressed because it's used by Android Studio to
-                        // open the generated HRF file.
-                        logger.warn(
-                            property = { true },
-                            propertyName = null,
-                            message = """
-                            A baseline profile was generated for the variant `${variantName.get()}`:
-                            ${Path(absolutePath).toUri()}
-                        """.trimIndent()
-                        )
-                    }
-                }
-            }
+        writeProfile(
+            filename = BASELINE_PROFILE_FILENAME,
+            rules = filteredBaselineProfileRules,
+            profileType = "baseline"
+        )
 
         // If this is a library we can stop here and don't manage the startup profiles.
         if (library.get()) {
@@ -324,36 +308,72 @@
         }
 
         // Use same sorting without filter for startup profiles.
-        val sortedProfileRules = startupRules
+        val sortedStartupProfileRules = startupRules
             .asSequence()
             .sorted()
             .mapNotNull { ProfileRule.parse(it) }
             .groupBy { it.classDescriptor + it.methodDescriptor }
             .map { it.value[0] }
             .sortedWith(ProfileRule.comparator)
-            .map { it.underlying }
-            .toList()
 
+        writeProfile(
+            filename = STARTUP_PROFILE_FILENAME,
+            profileType = "startup",
+            rules = sortedStartupProfileRules,
+        )
+    }
+
+    private fun writeProfile(filename: String, rules: List<ProfileRule>, profileType: String) {
         baselineProfileDir
-            .file(STARTUP_PROFILE_FILENAME)
+            .file(filename)
             .get()
             .asFile
             .apply {
+
+                // If an old profile file already exists calculate stats.
+                val stats = if (exists()) ProfileStats.from(
+                    existingRules = readLines().mapNotNull { ProfileRule.parse(it) },
+                    newRules = rules
+                ) else null
+
                 delete()
-                if (sortedProfileRules.isNotEmpty()) {
-                    writeText(sortedProfileRules.joinToString(System.lineSeparator()))
-                    if (lastTask.get()) {
-                        // This log should not be suppressed because it's used by Android Studio to
-                        // open the generated HRF file.
-                        logger.warn(
-                            property = { true },
-                            propertyName = null,
-                            message = """
-                            A startup profile was generated for the variant `${variantName.get()}`:
-                            ${Path(absolutePath).toUri()}
+                if (rules.isEmpty()) return
+                writeText(rules.joinToString(System.lineSeparator()) { it.underlying })
+
+                // If this is the last task display a success message (depending on the flag
+                // `saveInSrc` this task may be configured as a merge or copy task).
+                if (!lastTask.get()) {
+                    return
+                }
+
+                // This log should not be suppressed because it's used by Android Studio to
+                // open the generated HRF file.
+                logger.warn(
+                    property = { true },
+                    propertyName = null,
+                    message = """
+
+                    A $profileType profile was generated for the variant `${variantName.get()}`:
+                    ${Path(absolutePath).toUri()}
                         """.trimIndent()
-                        )
-                    }
+                )
+
+                // Print stats if was previously calculated
+                stats?.apply {
+                    logger.warn(
+                        property = { true },
+                        propertyName = null,
+                        message = """
+
+                    Comparison with previous $profileType profile:
+                      $existing Old rules
+                      $new New rules
+                      $added Added rules (${"%.2f".format(addedRatio * 100)}%)
+                      $removed Removed rules (${"%.2f".format(removedRatio * 100)}%)
+                      $unmodified Unmodified rules (${"%.2f".format(unmodifiedRatio * 100)}%)
+
+                      """.trimIndent()
+                    )
                 }
             }
     }
@@ -397,3 +417,54 @@
         .filter(filterBlock)
         .flatMap { it.readLines() }
 }
+
+internal data class ProfileStats(
+    val existing: Int,
+    val new: Int,
+    val added: Int,
+    val removed: Int,
+    val unmodified: Int,
+    val addedRatio: Float,
+    val removedRatio: Float,
+    val unmodifiedRatio: Float,
+) {
+    companion object {
+        fun from(existingRules: List<ProfileRule>, newRules: List<ProfileRule>): ProfileStats {
+            val existingRulesSet = existingRules
+                .map { "${it.classDescriptor}:${it.methodDescriptor}" }
+                .toHashSet()
+            val newRulesSet = newRules
+                .map { "${it.classDescriptor}:${it.methodDescriptor}" }
+                .toHashSet()
+            val allUniqueRules = existingRulesSet.union(newRulesSet).size
+
+            var unmodified = 0
+            var added = 0
+            var removed = 0
+
+            for (x in existingRulesSet) {
+                if (x in newRulesSet) {
+                    unmodified++
+                } else {
+                    removed++
+                }
+            }
+            for (x in newRulesSet) {
+                if (x !in existingRulesSet) {
+                    added++
+                }
+            }
+
+            return ProfileStats(
+                existing = existingRulesSet.size,
+                new = newRulesSet.size,
+                unmodified = unmodified,
+                added = added,
+                removed = removed,
+                addedRatio = added.toFloat() / allUniqueRules,
+                removedRatio = removed.toFloat() / allUniqueRules,
+                unmodifiedRatio = unmodified.toFloat() / allUniqueRules
+            )
+        }
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 00df0cb..589cad5d 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -1345,6 +1345,119 @@
             contains("Variant `benchmarkRelease` is disabled.")
         }
     }
+
+    @Test
+    fun testProfileStats() {
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN
+        )
+
+        // Test no previous execution
+        projectSetup.producer.setupWithoutFlavors(
+            releaseProfileLines = listOf(
+                Fixtures.CLASS_1_METHOD_1,
+                Fixtures.CLASS_1,
+            ),
+            releaseStartupProfileLines = listOf(
+                Fixtures.CLASS_1_METHOD_1,
+                Fixtures.CLASS_1,
+            )
+        )
+        gradleRunner.build("generateBaselineProfile") {
+            val notFound = it.lines().requireInOrder(
+                "Comparison with previous baseline profile:",
+                "Comparison with previous startup profile:",
+            )
+            assertThat(notFound.size).isEqualTo(2)
+        }
+
+        // Test unchanged
+        gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
+            println(it)
+            val notFound = it.lines().requireInOrder(
+                "Comparison with previous baseline profile:",
+                "  2 Old rules",
+                "  2 New rules",
+                "  0 Added rules (0.00%)",
+                "  0 Removed rules (0.00%)",
+                "  2 Unmodified rules (100.00%)",
+
+                "Comparison with previous startup profile:",
+                "  2 Old rules",
+                "  2 New rules",
+                "  0 Added rules (0.00%)",
+                "  0 Removed rules (0.00%)",
+                "  2 Unmodified rules (100.00%)",
+            )
+            assertThat(notFound).isEmpty()
+        }
+
+        // Test added
+        projectSetup.producer.setupWithoutFlavors(
+            releaseProfileLines = listOf(
+                Fixtures.CLASS_1_METHOD_1,
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_2_METHOD_2,
+                Fixtures.CLASS_2,
+            ),
+            releaseStartupProfileLines = listOf(
+                Fixtures.CLASS_1_METHOD_1,
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_2_METHOD_2,
+                Fixtures.CLASS_2,
+            )
+        )
+        gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
+            println(it)
+            val notFound = it.lines().requireInOrder(
+                "Comparison with previous baseline profile:",
+                "  2 Old rules",
+                "  4 New rules",
+                "  2 Added rules (50.00%)",
+                "  0 Removed rules (0.00%)",
+                "  2 Unmodified rules (50.00%)",
+
+                "Comparison with previous startup profile:",
+                "  2 Old rules",
+                "  4 New rules",
+                "  2 Added rules (50.00%)",
+                "  0 Removed rules (0.00%)",
+                "  2 Unmodified rules (50.00%)",
+            )
+            assertThat(notFound).isEmpty()
+        }
+
+        // Test removed
+        projectSetup.producer.setupWithoutFlavors(
+            releaseProfileLines = listOf(
+                Fixtures.CLASS_2_METHOD_2,
+                Fixtures.CLASS_2,
+            ),
+            releaseStartupProfileLines = listOf(
+                Fixtures.CLASS_2_METHOD_2,
+                Fixtures.CLASS_2,
+            )
+        )
+        gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
+            println(it)
+            val notFound = it.lines().requireInOrder(
+                "Comparison with previous baseline profile:",
+                "  4 Old rules",
+                "  2 New rules",
+                "  0 Added rules (0.00%)",
+                "  2 Removed rules (50.00%)",
+                "  2 Unmodified rules (50.00%)",
+
+                "Comparison with previous startup profile:",
+                "  4 Old rules",
+                "  2 New rules",
+                "  0 Added rules (0.00%)",
+                "  2 Removed rules (50.00%)",
+                "  2 Unmodified rules (50.00%)",
+            )
+            assertThat(notFound).isEmpty()
+        }
+    }
 }
 
 @RunWith(JUnit4::class)
diff --git a/binarycompatibilityvalidator/OWNERS b/binarycompatibilityvalidator/OWNERS
new file mode 100644
index 0000000..a43f7c1
--- /dev/null
+++ b/binarycompatibilityvalidator/OWNERS
@@ -0,0 +1,3 @@
+jeffrygaston@google.com
+aurimas@google.com
+fsladkey@google.com
\ No newline at end of file
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/build.gradle b/binarycompatibilityvalidator/binarycompatibilityvalidator/build.gradle
new file mode 100644
index 0000000..aa18e365
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 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.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+
+import androidx.build.LibraryType
+
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    implementation(libs.kotlinCompiler)
+    testImplementation(libs.truth)
+    testImplementation(libs.junit)
+}
+
+androidx {
+    name = "AndroidX Binary Compatibility Validator"
+    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
+    inceptionYear = "2024"
+    description = "Enforce binary compatibility for klibs"
+}
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/AbiExtensions.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/AbiExtensions.kt
new file mode 100644
index 0000000..871247d
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/AbiExtensions.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalLibraryAbiReader::class)
+
+package androidx.binarycompatibilityvalidator
+
+import org.jetbrains.kotlin.library.abi.AbiClassifierReference
+import org.jetbrains.kotlin.library.abi.AbiQualifiedName
+import org.jetbrains.kotlin.library.abi.AbiType
+import org.jetbrains.kotlin.library.abi.AbiTypeArgument
+import org.jetbrains.kotlin.library.abi.AbiTypeNullability
+import org.jetbrains.kotlin.library.abi.AbiVariance
+import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader
+
+// Convenience extensions for accessing properties that may exist without have to cast repeatedly
+// For sources with documentation see https://github.com/JetBrains/kotlin/blob/master/compiler/util-klib-abi/src/org/jetbrains/kotlin/library/abi/LibraryAbi.kt
+
+/** A classifier reference is either a simple class or a type reference **/
+internal val AbiType.classifierReference: AbiClassifierReference?
+    get() = (this as? AbiType.Simple)?.classifierReference
+/** The class name from a regular type e.g. 'Array' **/
+internal val AbiType.className: AbiQualifiedName?
+    get() = classifierReference?.className
+/** A tag from a type type parameter reference e.g. 'T' **/
+internal val AbiType.tag: String?
+    get() = classifierReference?.tag
+/** The string representation of a type, whether it is a simple type or a type reference **/
+internal val AbiType.classNameOrTag: String?
+    get() = className?.toString() ?: tag
+internal val AbiType.nullability: AbiTypeNullability?
+    get() = (this as? AbiType.Simple)?.nullability
+internal val AbiType.arguments: List<AbiTypeArgument>?
+    get() = (this as? AbiType.Simple)?.arguments
+internal val AbiTypeArgument.type: AbiType?
+    get() = (this as? AbiTypeArgument.TypeProjection)?.type
+internal val AbiTypeArgument.variance: AbiVariance?
+    get() = (this as? AbiTypeArgument.TypeProjection)?.variance
+private val AbiClassifierReference.className: AbiQualifiedName?
+    get() = (this as? AbiClassifierReference.ClassReference)?.className
+private val AbiClassifierReference.tag: String?
+    get() = (this as? AbiClassifierReference.TypeParameterReference)?.tag
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/Cursor.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/Cursor.kt
new file mode 100644
index 0000000..52f0e9b
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/Cursor.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.binarycompatibilityvalidator
+
+class Cursor private constructor(
+    private val lines: List<String>,
+    private var rowIndex: Int = 0,
+    private var columnIndex: Int = 0
+) {
+    constructor(text: String) : this(text.split("\n"))
+    val currentLine: String
+        get() = lines[rowIndex].slice(columnIndex until lines[rowIndex].length)
+    fun hasNext() = rowIndex < (lines.size - 1)
+    fun nextLine() {
+        rowIndex++
+        columnIndex = 0
+    }
+
+    fun parseSymbol(
+        pattern: String,
+        peek: Boolean = false,
+        skipInlineWhitespace: Boolean = true
+    ): String? {
+        val match = Regex(pattern).find(currentLine)
+        return match?.value?.also {
+            if (!peek) {
+                val offset = it.length + currentLine.indexOf(it)
+                columnIndex += offset
+                if (skipInlineWhitespace) {
+                    skipInlineWhitespace()
+                }
+            }
+        }
+    }
+
+    fun parseValidIdentifier(peek: Boolean = false): String? =
+        parseSymbol("^[a-zA-Z_][a-zA-Z0-9_]+", peek)
+
+    fun parseWord(peek: Boolean = false): String? = parseSymbol("[a-zA-Z]+", peek)
+
+    fun copy() = Cursor(lines, rowIndex, columnIndex)
+
+    internal fun skipInlineWhitespace() {
+        while (currentLine.firstOrNull()?.isWhitespace() == true) {
+            columnIndex++
+        }
+    }
+}
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/CursorExtensions.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/CursorExtensions.kt
new file mode 100644
index 0000000..2afb33a
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/CursorExtensions.kt
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+// Impl classes from kotlin.library.abi.impl are necessary to instantiate parsed declarations
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@file:OptIn(ExperimentalLibraryAbiReader::class)
+
+package androidx.binarycompatibilityvalidator
+
+import org.jetbrains.kotlin.library.abi.AbiClassKind
+import org.jetbrains.kotlin.library.abi.AbiCompoundName
+import org.jetbrains.kotlin.library.abi.AbiModality
+import org.jetbrains.kotlin.library.abi.AbiPropertyKind
+import org.jetbrains.kotlin.library.abi.AbiQualifiedName
+import org.jetbrains.kotlin.library.abi.AbiType
+import org.jetbrains.kotlin.library.abi.AbiTypeArgument
+import org.jetbrains.kotlin.library.abi.AbiTypeNullability
+import org.jetbrains.kotlin.library.abi.AbiTypeParameter
+import org.jetbrains.kotlin.library.abi.AbiValueParameter
+import org.jetbrains.kotlin.library.abi.AbiVariance
+import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader
+import org.jetbrains.kotlin.library.abi.impl.AbiTypeParameterImpl
+import org.jetbrains.kotlin.library.abi.impl.AbiValueParameterImpl
+import org.jetbrains.kotlin.library.abi.impl.ClassReferenceImpl
+import org.jetbrains.kotlin.library.abi.impl.SimpleTypeImpl
+import org.jetbrains.kotlin.library.abi.impl.StarProjectionImpl
+import org.jetbrains.kotlin.library.abi.impl.TypeParameterReferenceImpl
+import org.jetbrains.kotlin.library.abi.impl.TypeProjectionImpl
+
+// This file contains Cursor methods specific to parsing klib dump files
+
+internal fun Cursor.parseAbiModality(): AbiModality? {
+    val parsed = parseAbiModalityString(peek = true)?.let {
+        AbiModality.valueOf(it)
+    }
+    if (parsed != null) {
+        parseAbiModalityString()
+    }
+    return parsed
+}
+
+internal fun Cursor.parseClassKind(peek: Boolean = false): AbiClassKind? {
+    val parsed = parseClassKindString(peek = true)?.let {
+        AbiClassKind.valueOf(it)
+    }
+    if (parsed != null && !peek) {
+        parseClassKindString()
+    }
+    return parsed
+}
+
+internal fun Cursor.parsePropertyKind(peek: Boolean = false): AbiPropertyKind? {
+    val parsed = parsePropertyKindString(peek = true)?.let {
+        AbiPropertyKind.valueOf(it)
+    }
+    if (parsed != null && !peek) {
+        parsePropertyKindString()
+    }
+    return parsed
+}
+
+internal fun Cursor.hasClassKind(): Boolean {
+    val subCursor = copy()
+    subCursor.skipInlineWhitespace()
+    subCursor.parseAbiModality()
+    subCursor.parseClassModifiers()
+    return subCursor.parseClassKind() != null
+}
+
+internal fun Cursor.hasFunctionKind(): Boolean {
+    val subCursor = copy()
+    subCursor.skipInlineWhitespace()
+    subCursor.parseAbiModality()
+    subCursor.parseFunctionModifiers()
+    return subCursor.parseFunctionKind() != null
+}
+
+internal fun Cursor.hasPropertyKind(): Boolean {
+    val subCursor = copy()
+    subCursor.skipInlineWhitespace()
+    subCursor.parseAbiModality()
+    return subCursor.parsePropertyKind() != null
+}
+
+internal fun Cursor.hasGetter() = hasPropertyAccessor(GetterOrSetter.GETTER)
+internal fun Cursor.hasSetter() = hasPropertyAccessor(GetterOrSetter.SETTER)
+
+internal fun Cursor.hasGetterOrSetter() = hasGetter() || hasSetter()
+
+internal fun Cursor.parseGetterName(peek: Boolean = false): String? {
+    val cursor = subCursor(peek)
+    cursor.parseSymbol("^<get\\-") ?: return null
+    val name = cursor.parseValidIdentifier() ?: return null
+    cursor.parseSymbol("^>") ?: return null
+    return "<get-$name>"
+}
+
+internal fun Cursor.parseSetterName(peek: Boolean = false): String? {
+    val cursor = subCursor(peek)
+    cursor.parseSymbol("^<set\\-") ?: return null
+    val name = cursor.parseValidIdentifier() ?: return null
+    cursor.parseSymbol("^>") ?: return null
+    return "<set-$name>"
+}
+
+internal fun Cursor.parseGetterOrSetterName(peek: Boolean = false) =
+    parseGetterName(peek) ?: parseSetterName(peek)
+
+internal fun Cursor.parseClassModifier(peek: Boolean = false): String? =
+    parseSymbol("^(inner|value|fun|open|annotation|enum)", peek)
+
+internal fun Cursor.parseClassModifiers(): Set<String> {
+    val modifiers = mutableSetOf<String>()
+    while (parseClassModifier(peek = true) != null) {
+        modifiers.add(parseClassModifier()!!)
+    }
+    return modifiers
+}
+
+internal fun Cursor.parseFunctionKind(peek: Boolean = false) =
+    parseSymbol("^(constructor|fun)", peek)
+
+internal fun Cursor.parseFunctionModifier(peek: Boolean = false): String? =
+    parseSymbol("^(inline|suspend)", peek)
+
+internal fun Cursor.parseFunctionModifiers(): Set<String> {
+    val modifiers = mutableSetOf<String>()
+    while (parseFunctionModifier(peek = true) != null) {
+        modifiers.add(parseFunctionModifier()!!)
+    }
+    return modifiers
+}
+
+internal fun Cursor.parseAbiQualifiedName(peek: Boolean = false): AbiQualifiedName? {
+    val symbol = parseSymbol("^[a-zA-Z0-9\\.]+\\/[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?", peek)
+        ?: return null
+    val (packageName, relativeName) = symbol.split("/")
+    return AbiQualifiedName(
+        AbiCompoundName(packageName),
+        AbiCompoundName(relativeName)
+    )
+}
+
+internal fun Cursor.parseAbiType(peek: Boolean = false): AbiType? {
+    val cursor = subCursor(peek)
+    // A type will either be a qualified name (kotlin/Array) or a type reference (#A)
+    // try to parse a qualified name and a type reference if it doesn't exist
+    val abiQualifiedName = cursor.parseAbiQualifiedName()
+        ?: return cursor.parseTypeReference()
+    val typeArgs = cursor.parseTypeArgs() ?: emptyList()
+    val nullability = cursor.parseNullability(assumeNotNull = true)
+    return SimpleTypeImpl(
+        ClassReferenceImpl(abiQualifiedName),
+        arguments = typeArgs,
+        nullability = nullability
+    )
+}
+
+internal fun Cursor.parseTypeArgs(): List<AbiTypeArgument>? {
+    val typeArgsString = parseTypeParamsString() ?: return null
+    val subCursor = Cursor(typeArgsString)
+    subCursor.parseSymbol("<") ?: return null
+    val typeArgs = mutableListOf<AbiTypeArgument>()
+    while (subCursor.parseTypeArg(peek = true) != null) {
+        typeArgs.add(subCursor.parseTypeArg()!!)
+        subCursor.parseSymbol(",")
+    }
+    return typeArgs
+}
+
+internal fun Cursor.parseTypeArg(peek: Boolean = false): AbiTypeArgument? {
+    val cursor = subCursor(peek)
+    val variance = cursor.parseAbiVariance()
+    cursor.parseSymbol("\\*")?.let {
+        return StarProjectionImpl
+    }
+    val type = cursor.parseAbiType(peek) ?: return null
+    return TypeProjectionImpl(
+        type = type,
+        variance = variance
+    )
+}
+
+internal fun Cursor.parseAbiVariance(): AbiVariance {
+    val variance = parseSymbol("^(out|in)") ?: return AbiVariance.INVARIANT
+    return AbiVariance.valueOf(variance.uppercase())
+}
+
+internal fun Cursor.parseTypeReference(): AbiType? {
+    val typeParamReference = parseTag() ?: return null
+    val typeArgs = parseTypeArgs() ?: emptyList()
+    val nullability = parseNullability()
+    return SimpleTypeImpl(
+        TypeParameterReferenceImpl(typeParamReference),
+        arguments = typeArgs,
+        nullability = nullability
+    )
+}
+
+internal fun Cursor.parseTag() = parseSymbol("^#[a-zA-Z0-9]+")?.removePrefix("#")
+
+internal fun Cursor.parseNullability(assumeNotNull: Boolean = false): AbiTypeNullability {
+    val nullable = parseSymbol("^\\?") != null
+    val definitelyNotNull = parseSymbol("^\\!\\!") != null
+    return when {
+        nullable -> AbiTypeNullability.MARKED_NULLABLE
+        definitelyNotNull -> AbiTypeNullability.DEFINITELY_NOT_NULL
+        else -> if (assumeNotNull) {
+            AbiTypeNullability.DEFINITELY_NOT_NULL
+        } else {
+            AbiTypeNullability.NOT_SPECIFIED
+        }
+    }
+}
+
+internal fun Cursor.parseSuperTypes(): MutableSet<AbiType> {
+    parseSymbol(":")
+    val superTypes = mutableSetOf<AbiType>()
+    while (parseAbiQualifiedName(peek = true) != null) {
+        superTypes.add(parseAbiType()!!)
+        parseSymbol(",")
+    }
+    return superTypes
+}
+
+fun Cursor.parseTypeParams(peek: Boolean = false): List<AbiTypeParameter>? {
+    val typeParamsString = parseTypeParamsString(peek) ?: return null
+    val subCursor = Cursor(typeParamsString)
+    subCursor.parseSymbol("^<")
+    val typeParams = mutableListOf<AbiTypeParameter>()
+    while (subCursor.parseTypeParam(peek = true) != null) {
+        typeParams.add(subCursor.parseTypeParam()!!)
+        subCursor.parseSymbol("^,")
+    }
+    return typeParams
+}
+
+fun Cursor.parseTypeParam(peek: Boolean = false): AbiTypeParameter? {
+    val cursor = subCursor(peek)
+    val tag = cursor.parseTag() ?: return null
+    cursor.parseSymbol("^:")
+    val variance = cursor.parseAbiVariance()
+    val isReified = cursor.parseSymbol("reified") != null
+    val upperBounds = mutableListOf<AbiType>()
+    if (null != cursor.parseAbiType(peek = true)) {
+        upperBounds.add(cursor.parseAbiType()!!)
+    }
+
+    return AbiTypeParameterImpl(
+        tag = tag,
+        variance = variance,
+        isReified = isReified,
+        upperBounds = upperBounds
+    )
+}
+
+internal fun Cursor.parseValueParameters(): List<AbiValueParameter>? {
+    val valueParamString = parseValueParametersString() ?: return null
+    val subCursor = Cursor(valueParamString)
+    val valueParams = mutableListOf<AbiValueParameter>()
+    subCursor.parseSymbol("\\(")
+    while (null != subCursor.parseValueParameter(peek = true)) {
+        valueParams.add(subCursor.parseValueParameter()!!)
+        subCursor.parseSymbol("^,")
+    }
+    return valueParams
+}
+
+internal fun Cursor.parseValueParameter(peek: Boolean = false): AbiValueParameter? {
+    val cursor = subCursor(peek)
+    val modifiers = cursor.parseValueParameterModifiers()
+    val isNoInline = modifiers.contains("noinline")
+    val isCrossinline = modifiers.contains("crossinline")
+    val type = cursor.parseAbiType() ?: return null
+    val isVararg = cursor.parseVarargSymbol() != null
+    val hasDefaultArg = cursor.parseDefaultArg() != null
+    return AbiValueParameterImpl(
+        type = type,
+        isVararg = isVararg,
+        hasDefaultArg = hasDefaultArg,
+        isNoinline = isNoInline,
+        isCrossinline = isCrossinline
+    )
+}
+
+internal fun Cursor.parseValueParameterModifiers(): Set<String> {
+    val modifiers = mutableSetOf<String>()
+    while (parseValueParameterModifier(peek = true) != null) {
+        modifiers.add(parseValueParameterModifier()!!)
+    }
+    return modifiers
+}
+
+internal fun Cursor.parseValueParameterModifier(peek: Boolean = false): String? =
+    parseSymbol("^(crossinline|noinline)", peek)
+
+internal fun Cursor.parseVarargSymbol() = parseSymbol("^\\.\\.\\.")
+
+internal fun Cursor.parseDefaultArg() = parseSymbol("^=\\.\\.\\.")
+
+internal fun Cursor.parseFunctionReceiver(): AbiType? {
+    val string = parseFunctionReceiverString() ?: return null
+    val subCursor = Cursor(string)
+    subCursor.parseSymbol("\\(")
+    return subCursor.parseAbiType()
+}
+
+internal fun Cursor.parseReturnType(): AbiType? {
+    parseSymbol("^:\\s")
+    return parseAbiType()
+}
+
+internal fun Cursor.parseTargets(): List<String> {
+    parseSymbol("^Targets:")
+    parseSymbol("^\\[")
+    val targets = mutableListOf<String>()
+    while (parseValidIdentifier(peek = true) != null) {
+        targets.add(parseValidIdentifier()!!)
+        parseSymbol("^,")
+    }
+    parseSymbol("^\\]")
+    return targets
+}
+
+/**
+ * Used to check if declarations after a property are getter / setter methods which should be
+ * attached to that property.
+*/
+private fun Cursor.hasPropertyAccessor(type: GetterOrSetter): Boolean {
+    val subCursor = copy()
+    subCursor.parseAbiModality()
+    subCursor.parseFunctionModifiers()
+    subCursor.parseFunctionKind() ?: return false // if it's not a function it's not a getter/setter
+    val mightHaveTypeParams = subCursor.parseGetterOrSetterName(peek = true) == null
+    if (mightHaveTypeParams) {
+        subCursor.parseTypeParams()
+    }
+    subCursor.parseFunctionReceiver()
+    return when (type) {
+        GetterOrSetter.GETTER -> subCursor.parseGetterName() != null
+        GetterOrSetter.SETTER -> subCursor.parseSetterName() != null
+    }
+}
+
+private fun Cursor.subCursor(peek: Boolean) = if (peek) { copy() } else { this }
+
+private fun Cursor.parseTypeParamsString(peek: Boolean = false): String? {
+    val cursor = subCursor(peek)
+    val result = StringBuilder()
+    cursor.parseSymbol("^<")?.let { result.append(it) } ?: return null
+    var openBracketCount = 1
+    while (openBracketCount > 0) {
+        val nextSymbol = cursor.parseSymbol(".", skipInlineWhitespace = false).also {
+            result.append(it)
+        }
+        when (nextSymbol) {
+            "<" -> openBracketCount++
+            ">" -> openBracketCount--
+        }
+    }
+    cursor.skipInlineWhitespace()
+    return result.toString()
+}
+
+private fun Cursor.parseFunctionReceiverString() =
+    parseSymbol("^\\([a-zA-Z0-9,\\/<>,#\\.\\s]+?\\)\\.")
+
+private fun Cursor.parseValueParametersString() =
+    parseSymbol("^\\(([a-zA-Z0-9,\\/<>,#\\.\\s\\?=]+)?\\)")
+
+private fun Cursor.parseAbiModalityString(peek: Boolean = false) =
+    parseSymbol("^(final|open|abstract|sealed)", peek)?.uppercase()
+
+private fun Cursor.parsePropertyKindString(peek: Boolean = false) =
+    parseSymbol("^(const\\sval|val|var)", peek)?.uppercase()?.replace(" ", "_")
+
+private fun Cursor.parseClassKindString(peek: Boolean = false) =
+    parseSymbol("^(class|interface|object|enum_class|annotation_class)", peek)?.uppercase()
+
+private enum class GetterOrSetter() {
+    GETTER,
+    SETTER
+}
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorExtensionsTest.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorExtensionsTest.kt
new file mode 100644
index 0000000..6390ea9
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorExtensionsTest.kt
@@ -0,0 +1,509 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalLibraryAbiReader::class)
+
+package androidx.binarycompatibilityvalidator
+
+import com.google.common.truth.Truth.assertThat
+import org.jetbrains.kotlin.library.abi.AbiClassKind
+import org.jetbrains.kotlin.library.abi.AbiModality
+import org.jetbrains.kotlin.library.abi.AbiPropertyKind
+import org.jetbrains.kotlin.library.abi.AbiTypeNullability
+import org.jetbrains.kotlin.library.abi.AbiValueParameter
+import org.jetbrains.kotlin.library.abi.AbiVariance
+import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader
+import org.junit.Test
+
+class CursorExtensionsTest {
+
+    @Test
+    fun parseModalityFailure() {
+        val input = "something else"
+        val cursor = Cursor(input)
+        val modality = cursor.parseAbiModality()
+        assertThat(modality).isNull()
+        assertThat(cursor.currentLine).isEqualTo("something else")
+    }
+
+    @Test
+    fun parseModalitySuccess() {
+        val input = "final whatever"
+        val cursor = Cursor(input)
+        val modality = cursor.parseAbiModality()
+        assertThat(modality).isEqualTo(AbiModality.FINAL)
+        assertThat(cursor.currentLine).isEqualTo("whatever")
+    }
+
+    @Test
+    fun parseClassModifier() {
+        val input = "inner whatever"
+        val cursor = Cursor(input)
+        val modifier = cursor.parseClassModifier()
+        assertThat(modifier).isEqualTo("inner")
+        assertThat(cursor.currentLine).isEqualTo("whatever")
+    }
+
+    @Test
+    fun parseClassModifiers() {
+        val input = "inner value fun whatever"
+        val cursor = Cursor(input)
+        val modifiers = cursor.parseClassModifiers()
+        assertThat(modifiers).containsExactly("inner", "fun", "value")
+        assertThat(cursor.currentLine).isEqualTo("whatever")
+    }
+
+    @Test
+    fun parseFunctionModifiers() {
+        val input = "final inline suspend fun component1(): kotlin/Long"
+        val cursor = Cursor(input)
+        cursor.parseAbiModality()
+        val modifiers = cursor.parseFunctionModifiers()
+        assertThat(modifiers).containsExactly("inline", "suspend")
+        assertThat(cursor.currentLine).isEqualTo("fun component1(): kotlin/Long")
+    }
+
+    @Test
+    fun parseClassKindSimple() {
+        val input = "class"
+        val cursor = Cursor(input)
+        val kind = cursor.parseClassKind()
+        assertThat(kind).isEqualTo(AbiClassKind.CLASS)
+    }
+
+    @Test
+    fun parseClassKindFalsePositive() {
+        val input = "androidx.collection/objectFloatMap"
+        val cursor = Cursor(input)
+        val kind = cursor.parseClassKind()
+        assertThat(kind).isNull()
+    }
+
+    @Test
+    fun hasClassKind() {
+        val input = "final class my.lib/MyClass"
+        val cursor = Cursor(input)
+        assertThat(cursor.hasClassKind()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test
+    fun parseFunctionKindSimple() {
+        val input = "fun hello"
+        val cursor = Cursor(input)
+        val kind = cursor.parseFunctionKind()
+        assertThat(kind).isEqualTo("fun")
+        assertThat(cursor.currentLine).isEqualTo(cursor.currentLine)
+    }
+
+    @Test fun hasFunctionKind() {
+        val input = "    final fun myFun(): kotlin/String "
+        val cursor = Cursor(input)
+        assertThat(cursor.hasFunctionKind()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test fun hasFunctionKindConstructor() {
+        val input = "    constructor <init>(kotlin/Int =...)"
+        val cursor = Cursor(input)
+        assertThat(cursor.hasFunctionKind()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test fun parseGetterOrSetterName() {
+        val input = "<get-indices>()"
+        val cursor = Cursor(input)
+        val name = cursor.parseGetterOrSetterName()
+        assertThat(name).isEqualTo("<get-indices>")
+        assertThat(cursor.currentLine).isEqualTo("()")
+    }
+
+    @Test fun hasGetter() {
+        val input = "final inline fun <get-indices>(): kotlin.ranges/IntRange"
+        val cursor = Cursor(input)
+        assertThat(cursor.hasGetter()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test fun hasSetter() {
+        val input = "final inline fun <set-indices>(): kotlin.ranges/IntRange"
+        val cursor = Cursor(input)
+        assertThat(cursor.hasSetter()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test fun hasGetterOrSetter() {
+        val inputs = listOf(
+            "final inline fun <set-indices>(): kotlin.ranges/IntRange",
+            "final inline fun <get-indices>(): kotlin.ranges/IntRange"
+        )
+        inputs.forEach { input ->
+            assertThat(Cursor(input).hasGetterOrSetter()).isTrue()
+        }
+    }
+
+    @Test
+    fun hasPropertyKind() {
+        val input = "final const val my.lib/myProp"
+        val cursor = Cursor(input)
+        assertThat(cursor.hasPropertyKind()).isTrue()
+        assertThat(cursor.currentLine).isEqualTo(input)
+    }
+
+    @Test
+    fun parsePropertyKindConstVal() {
+        val input = "const val something"
+        val cursor = Cursor(input)
+        val kind = cursor.parsePropertyKind()
+        assertThat(kind).isEqualTo(AbiPropertyKind.CONST_VAL)
+        assertThat(cursor.currentLine).isEqualTo("something")
+    }
+
+    @Test
+    fun parsePropertyKindVal() {
+        val input = "val something"
+        val cursor = Cursor(input)
+        val kind = cursor.parsePropertyKind()
+        assertThat(kind).isEqualTo(AbiPropertyKind.VAL)
+        assertThat(cursor.currentLine).isEqualTo("something")
+    }
+
+    @Test
+    fun parseNullability() {
+        val nullable = Cursor("?").parseNullability()
+        val notNull = Cursor("!!").parseNullability()
+        val unspecified = Cursor("another symbol").parseNullability()
+        assertThat(nullable).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+        assertThat(notNull).isEqualTo(AbiTypeNullability.DEFINITELY_NOT_NULL)
+        assertThat(unspecified).isEqualTo(AbiTypeNullability.NOT_SPECIFIED)
+    }
+
+    @Test fun parseNullabilityWhenAssumingNotNullable() {
+        val unspecified = Cursor("").parseNullability(assumeNotNull = true)
+        assertThat(unspecified).isEqualTo(AbiTypeNullability.DEFINITELY_NOT_NULL)
+    }
+
+    @Test fun parseQualifiedName() {
+        val input = "androidx.collection/MutableScatterMap something"
+        val cursor = Cursor(input)
+        val qName = cursor.parseAbiQualifiedName()
+        assertThat(qName.toString()).isEqualTo("androidx.collection/MutableScatterMap")
+        assertThat(cursor.currentLine).isEqualTo("something")
+    }
+
+    @Test fun parseQualifiedNameKotlin() {
+        val input = "kotlin/Function2<#A1, #A, #A1>"
+        val cursor = Cursor(input)
+        val qName = cursor.parseAbiQualifiedName()
+        assertThat(qName.toString()).isEqualTo("kotlin/Function2")
+        assertThat(cursor.currentLine).isEqualTo("<#A1, #A, #A1>",)
+    }
+
+    @Test fun parseQualifie0dNameDoesNotGrabNullable() {
+        val input = "androidx.collection/MutableScatterMap? something"
+        val cursor = Cursor(input)
+        val qName = cursor.parseAbiQualifiedName()
+        assertThat(qName.toString()).isEqualTo("androidx.collection/MutableScatterMap")
+        assertThat(cursor.currentLine).isEqualTo("? something")
+    }
+
+    @Test
+    fun parseAbiType() {
+        val input = "androidx.collection/ScatterMap<#A, #B> something"
+        val cursor = Cursor(input)
+        val type = cursor.parseAbiType()
+        assertThat(type?.className?.toString()).isEqualTo(
+            "androidx.collection/ScatterMap"
+        )
+        assertThat(cursor.currentLine).isEqualTo("something")
+    }
+
+    @Test
+    fun parseAbiTypeWithAnotherType() {
+        val input = "androidx.collection/ScatterMap<#A, #B>, androidx.collection/Other<#A, #B> " +
+            "something"
+        val cursor = Cursor(input)
+        val type = cursor.parseAbiType()
+        assertThat(type?.className?.toString()).isEqualTo(
+            "androidx.collection/ScatterMap"
+        )
+        assertThat(cursor.currentLine).isEqualTo(
+            ", androidx.collection/Other<#A, #B> something"
+        )
+    }
+
+    @Test fun parseAbiTypeWithThreeParams() {
+        val input = "kotlin/Function2<#A1, #A, #A1>"
+        val cursor = Cursor(input)
+        val type = cursor.parseAbiType()
+        assertThat(type?.className?.toString()).isEqualTo("kotlin/Function2")
+    }
+
+    @Test
+    fun parseSuperTypes() {
+        val input = ": androidx.collection/ScatterMap<#A, #B>, androidx.collection/Other<#A, #B> " +
+            "something"
+        val cursor = Cursor(input)
+        val superTypes = cursor.parseSuperTypes().toList()
+        assertThat(superTypes).hasSize(2)
+        assertThat(superTypes.first().className?.toString()).isEqualTo(
+            "androidx.collection/ScatterMap"
+        )
+        assertThat(superTypes.last().className?.toString()).isEqualTo(
+            "androidx.collection/Other"
+        )
+        assertThat(cursor.currentLine).isEqualTo("something")
+    }
+
+    @Test fun parseReturnType() {
+        val input = ": androidx.collection/ScatterMap<#A, #B> stuff"
+        val cursor = Cursor(input)
+        val returnType = cursor.parseReturnType()
+        assertThat(returnType?.className?.toString()).isEqualTo(
+            "androidx.collection/ScatterMap"
+        )
+        assertThat(cursor.currentLine).isEqualTo("stuff")
+    }
+
+    @Test fun parseReturnTypeNullableWithTypeParamsNullable() {
+        val input = ": #B? stuff"
+        val cursor = Cursor(input)
+        val returnType = cursor.parseReturnType()
+        assertThat(returnType?.tag).isEqualTo("B")
+        assertThat(returnType?.nullability).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+        assertThat(cursor.currentLine).isEqualTo("stuff")
+    }
+
+    @Test fun parseReturnTypeNullableWithTypeParamsNotSpecified() {
+        val input = ": #B stuff"
+        val cursor = Cursor(input)
+        val returnType = cursor.parseReturnType()
+        assertThat(returnType?.tag).isEqualTo("B")
+        assertThat(returnType?.nullability).isEqualTo(AbiTypeNullability.NOT_SPECIFIED)
+        assertThat(cursor.currentLine).isEqualTo("stuff")
+    }
+
+    @Test
+    fun parseFunctionReceiver() {
+        val input = "(androidx.collection/LongSparseArray<#A>).androidx.collection/keyIterator()"
+        val cursor = Cursor(input)
+        val receiver = cursor.parseFunctionReceiver()
+        assertThat(receiver?.className.toString()).isEqualTo(
+            "androidx.collection/LongSparseArray"
+        )
+        assertThat(cursor.currentLine).isEqualTo("androidx.collection/keyIterator()")
+    }
+
+    @Test
+    fun parseFunctionReceiver2() {
+        val input = "(androidx.collection/LongSparseArray<#A1>).<get-size>(): kotlin/Int"
+        val cursor = Cursor(input)
+        val receiver = cursor.parseFunctionReceiver()
+        assertThat(receiver?.className.toString()).isEqualTo(
+            "androidx.collection/LongSparseArray"
+        )
+        assertThat(cursor.currentLine).isEqualTo("<get-size>(): kotlin/Int")
+    }
+
+    @Test fun parseValueParamCrossinlineDefault() {
+        val input = "crossinline kotlin/Function2<#A, #B, kotlin/Int> =..."
+        val cursor = Cursor(input)
+        val valueParam = cursor.parseValueParameter()!!
+        assertThat(
+            valueParam.type.className.toString()
+        ).isEqualTo("kotlin/Function2")
+        assertThat(valueParam.hasDefaultArg).isTrue()
+        assertThat(valueParam.isCrossinline).isTrue()
+        assertThat(valueParam.isVararg).isFalse()
+    }
+
+    @Test
+    fun parseValueParamVararg() {
+        val input = "kotlin/Array<out kotlin/Pair<#A, #B>>..."
+        val cursor = Cursor(input)
+        val valueParam = cursor.parseValueParameter()
+        assertThat(
+            valueParam?.type?.className?.toString()
+        ).isEqualTo("kotlin/Array")
+        assertThat(valueParam?.hasDefaultArg).isFalse()
+        assertThat(valueParam?.isCrossinline).isFalse()
+        assertThat(valueParam?.isVararg).isTrue()
+    }
+
+    @Test fun parseValueParametersWithTypeArgs() {
+        val input = "kotlin/Array<out #A>..."
+        val cursor = Cursor(input)
+        val valueParam = cursor.parseValueParameter()
+        assertThat(valueParam?.type?.arguments).hasSize(1)
+    }
+
+    @Test fun parseValueParametersWithTwoTypeArgs() {
+        val input = "kotlin/Function1<kotlin/Double, kotlin/Boolean>)"
+        val cursor = Cursor(input)
+        val valueParam = cursor.parseValueParameter()
+        assertThat(valueParam?.type?.arguments).hasSize(2)
+    }
+
+    @Test fun parseValueParametersEmpty() {
+        val input = "() thing"
+        val cursor = Cursor(input)
+        val params = cursor.parseValueParameters()
+        assertThat(params).isEqualTo(emptyList<AbiValueParameter>())
+        assertThat(cursor.currentLine).isEqualTo("thing")
+    }
+
+    @Test fun parseValueParamsSimple() {
+        val input = "(kotlin/Function1<#A, kotlin/Boolean>)"
+        val cursor = Cursor(input)
+        val valueParams = cursor.parseValueParameters()
+        assertThat(valueParams).hasSize(1)
+    }
+
+    @Test fun parseValueParamsTwoArgs() {
+        val input = "(#A1, kotlin/Function2<#A1, #A, #A1>)"
+        val cursor = Cursor(input)
+        val valueParams = cursor.parseValueParameters()
+        assertThat(valueParams).hasSize(2)
+        assertThat(valueParams?.first()?.type?.tag).isEqualTo("A1")
+    }
+
+    @Test
+    fun parseValueParamsWithHasDefaultArg() {
+        val input = "(kotlin/Int =...)"
+        val cursor = Cursor(input)
+        val valueParams = cursor.parseValueParameters()
+        assertThat(valueParams).hasSize(1)
+        assertThat(valueParams?.single()?.hasDefaultArg).isTrue()
+    }
+
+    @Test
+    fun parseValueParamsComplex2() {
+        val input = "(kotlin/Int, crossinline kotlin/Function2<#A, #B, kotlin/Int> =..., " +
+            "crossinline kotlin/Function1<#A, #B?> =..., " +
+            "crossinline kotlin/Function4<kotlin/Boolean, #A, #B, #B?, kotlin/Unit> =...)"
+        val cursor = Cursor(input)
+        val valueParams = cursor.parseValueParameters()!!
+        assertThat(valueParams).hasSize(4)
+        assertThat(valueParams.first().type.className?.toString()).isEqualTo("kotlin/Int")
+        val rest = valueParams.subList(1, valueParams.size)
+        assertThat(rest).hasSize(3)
+        assertThat(rest.all { it.hasDefaultArg }).isTrue()
+        assertThat(rest.all { it.isCrossinline }).isTrue()
+    }
+
+    @Test fun parseValueParamsComplex3() {
+        val input = "(kotlin/Array<out kotlin/Pair<#A, #B>>...)"
+        val cursor = Cursor(input)
+        val valueParams = cursor.parseValueParameters()!!
+        assertThat(valueParams).hasSize(1)
+
+        assertThat(valueParams.single().isVararg).isTrue()
+        val type = valueParams.single().type
+        assertThat(type.className.toString()).isEqualTo("kotlin/Array")
+    }
+
+    @Test fun parseTypeParams() {
+        val input = "<#A1: kotlin/Any?>"
+        val cursor = Cursor(input)
+        val typeParams = cursor.parseTypeParams()
+        assertThat(typeParams).hasSize(1)
+        val type = typeParams?.single()?.upperBounds?.single()
+        assertThat(typeParams?.single()?.tag).isEqualTo("A1")
+        assertThat(type?.className?.toString()).isEqualTo("kotlin/Any")
+        assertThat(type?.nullability).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+        assertThat(typeParams?.single()?.variance).isEqualTo(AbiVariance.INVARIANT)
+    }
+
+    @Test fun parseTypeParamsWithVariance() {
+        val input = "<#A1: out kotlin/Any?>"
+        val cursor = Cursor(input)
+        val typeParams = cursor.parseTypeParams()
+        assertThat(typeParams).hasSize(1)
+        val type = typeParams?.single()?.upperBounds?.single()
+        assertThat(typeParams?.single()?.tag).isEqualTo("A1")
+        assertThat(type?.className?.toString()).isEqualTo("kotlin/Any")
+        assertThat(type?.nullability).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+        assertThat(typeParams?.single()?.variance).isEqualTo(AbiVariance.OUT)
+    }
+
+    @Test fun parseTypeParamsWithTwo() {
+        val input = "<#A: kotlin/Any?, #B: kotlin/Any?>"
+        val cursor = Cursor(input)
+        val typeParams = cursor.parseTypeParams()
+        assertThat(typeParams).hasSize(2)
+        val type1 = typeParams?.first()?.upperBounds?.single()
+        val type2 = typeParams?.first()?.upperBounds?.single()
+        assertThat(typeParams?.first()?.tag).isEqualTo("A")
+        assertThat(typeParams?.last()?.tag).isEqualTo("B")
+        assertThat(type1?.className?.toString()).isEqualTo("kotlin/Any")
+        assertThat(type1?.nullability).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+        assertThat(type2?.className?.toString()).isEqualTo("kotlin/Any")
+        assertThat(type2?.nullability).isEqualTo(AbiTypeNullability.MARKED_NULLABLE)
+    }
+
+    @Test fun parseTypeParamsReifed() {
+        val input = "<#A1: reified kotlin/Any?>"
+        val cursor = Cursor(input)
+        val typeParam = cursor.parseTypeParams()?.single()
+        assertThat(typeParam).isNotNull()
+        assertThat(typeParam?.isReified).isTrue()
+    }
+
+    @Test
+    fun parseTypeArgs() {
+        val input = "<out #A>"
+        val cursor = Cursor(input)
+        val typeArgs = cursor.parseTypeArgs()
+        assertThat(typeArgs).hasSize(1)
+        val typeArg = typeArgs?.single()
+        assertThat(typeArg?.type?.tag).isEqualTo("A")
+        assertThat(typeArg?.variance).isEqualTo(AbiVariance.OUT)
+    }
+
+    @Test
+    fun parseTwoTypeArgs() {
+        val input = "<kotlin/Double, kotlin/Boolean>"
+        val cursor = Cursor(input)
+        val typeArgs = cursor.parseTypeArgs()
+        assertThat(typeArgs).hasSize(2)
+        assertThat(typeArgs?.first()?.type?.className?.toString()).isEqualTo("kotlin/Double")
+        assertThat(typeArgs?.last()?.type?.className?.toString()).isEqualTo("kotlin/Boolean")
+    }
+
+    @Test
+    fun parseTypeArgsWithNestedBrackets() {
+        val input = "<androidx.collection/ScatterMap<#A, #B>, androidx.collection/Other<#A, #B>>," +
+            " something else"
+        val cursor = Cursor(input)
+        val typeArgs = cursor.parseTypeArgs()
+        assertThat(typeArgs).hasSize(2)
+        assertThat(cursor.currentLine).isEqualTo(", something else")
+    }
+
+    @Test fun parseVarargSymbol() {
+        val input = "..."
+        val cursor = Cursor(input)
+        val vararg = cursor.parseVarargSymbol()
+        assertThat(vararg).isNotNull()
+    }
+
+    @Test fun parseTargets() {
+        val input = "Targets: [iosX64, linuxX64]"
+        val cursor = Cursor(input)
+        val targets = cursor.parseTargets()
+        assertThat(targets).containsExactly("linuxX64", "iosX64")
+    }
+}
diff --git a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorTest.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorTest.kt
new file mode 100644
index 0000000..f37f6c5
--- /dev/null
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/test/java/androidx/binarycompatibilityvalidator/CursorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.binarycompatibilityvalidator
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class CursorTest {
+
+    @Test
+    fun cursorShowsCurrentLine() {
+        val input = "one\ntwo\nthree"
+        val cursor = Cursor(input)
+        assertThat(cursor.currentLine).isEqualTo("one")
+        cursor.nextLine()
+        assertThat(cursor.currentLine).isEqualTo("two")
+        cursor.nextLine()
+        assertThat(cursor.currentLine).isEqualTo("three")
+        assertThat(cursor.hasNext()).isFalse()
+    }
+
+    @Test
+    fun cursorGetsNextWord() {
+        val input = "one two three"
+        val cursor = Cursor(input)
+        val word = cursor.parseWord()
+        assertThat(word).isEqualTo("one")
+        assertThat("two three").isEqualTo(cursor.currentLine)
+    }
+
+    @Test
+    fun parseValidIdentifierValid() {
+        val input = "oneTwo3 four"
+        val cursor = Cursor(input)
+        val symbol = cursor.parseValidIdentifier()
+        assertThat(symbol).isEqualTo("oneTwo3")
+        assertThat("four").isEqualTo(cursor.currentLine)
+    }
+
+    @Test
+    fun parseValidIdentifierValidStartsWithUnderscore() {
+        val input = "_one_Two3 four"
+        val cursor = Cursor(input)
+        val symbol = cursor.parseValidIdentifier()
+        assertThat(symbol).isEqualTo("_one_Two3")
+        assertThat("four").isEqualTo(cursor.currentLine)
+    }
+
+    @Test
+    fun parseValidIdentifierInvalid() {
+        val input = "1twothree"
+        val cursor = Cursor(input)
+        val symbol = cursor.parseValidIdentifier()
+        assertThat(symbol).isNull()
+    }
+
+    @Test
+    fun skipWhitespace() {
+        val input = "    test"
+        val cursor = Cursor(input)
+        cursor.skipInlineWhitespace()
+        assertThat(cursor.currentLine).isEqualTo("test")
+    }
+
+    @Test
+    fun skipWhitespaceOnBlankLine() {
+        val input = ""
+        val cursor = Cursor(input)
+        cursor.skipInlineWhitespace()
+        assertThat(cursor.currentLine).isEqualTo("")
+    }
+}
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 5b3af52..4b77b51 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -57,7 +57,7 @@
     implementation(project(":benchmark:benchmark-gradle-plugin"))
     implementation(project(":inspection:inspection-gradle-plugin"))
     implementation(project(":stableaidl:stableaidl-gradle-plugin"))
-    implementation(findGradleKotlinDsl())
+    implementation(project.ext.findGradleKotlinDsl())
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(project(":internal-testutils-gradle-plugin"))
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
index 46f896e..02be011e 100644
--- a/buildSrc-tests/lint-baseline.xml
+++ b/buildSrc-tests/lint-baseline.xml
@@ -300,155 +300,11 @@
 
     <issue
         id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="                    rootProject.findProperty(ENABLE_ARG) != &quot;false&quot;"
-        errorLine2="                                ~~~~~~~~~~~~">
+        message="Use providers.gradleProperty instead of property"
+        errorLine1="            (this.rootProject.property(&quot;ext&quot;) as ExtraPropertiesExtension).set("
+        errorLine2="                              ~~~~~~~~">
         <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/dependencyTracker/AffectedModuleDetector.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="                    rootProject.findProperty(ENABLE_ARG) != &quot;false&quot;"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/dependencyTracker/AffectedModuleDetector.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="            val baseCommitOverride: String? = rootProject.findProperty(BASE_COMMIT_ARG) as String?"
-        errorLine2="                                                          ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/dependencyTracker/AffectedModuleDetector.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="            val baseCommitOverride: String? = rootProject.findProperty(BASE_COMMIT_ARG) as String?"
-        errorLine2="                                                          ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/dependencyTracker/AffectedModuleDetector.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="    override val compileSdk: Int by lazy { project.findProperty(COMPILE_SDK).toString().toInt() }"
-        errorLine2="                                                   ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/AndroidXConfig.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="        project.findProperty(COMPILE_SDK_EXTENSION) as Int?"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/AndroidXConfig.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="        project.findProperty(TARGET_SDK_VERSION).toString().toInt()"
-        errorLine2="                ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/AndroidXConfig.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="    project.findProperty(ALTERNATIVE_PROJECT_URL) as? String"
-        errorLine2="            ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXGradleProperties.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="    return (project.findProperty(ENABLE_DOCUMENTATION) as? String)?.toBoolean() ?: true"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXGradleProperties.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="fun Project.findBooleanProperty(propName: String) = (findProperty(propName) as? String)?.toBoolean()"
-        errorLine2="                                                     ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXGradleProperties.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="                return checkNotNull(findProperty(name)) {"
-        errorLine2="                                    ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXPlaygroundRootImplPlugin.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="        parseTargetPlatformsFlag(project.findProperty(ENABLED_KMP_TARGET_PLATFORMS) as? String)"
-        errorLine2="                                         ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/KmpPlatforms.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="            findProperty(DISABLE_COMPILER_DAEMON_FLAG)?.toString()?.toBoolean() == true"
-        errorLine2="            ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/KonanPrebuiltsSetup.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="            val value = project.findProperty(STUDIO_TYPE)?.toString()"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/ProjectLayoutType.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="            val value = project.findProperty(STUDIO_TYPE)?.toString()"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*3}/androidx/build/ProjectLayoutType.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="        val group = findProperty(&quot;group&quot;) as String"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/VersionFileWriterTask.kt"/>
-    </issue>
-
-    <issue
-        id="GradleProjectIsolation"
-        message="Use providers.gradleProperty instead of findProperty"
-        errorLine1="        val artifactId = findProperty(&quot;name&quot;) as String"
-        errorLine2="                         ~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/VersionFileWriterTask.kt"/>
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/AndroidXComposeImplPlugin.kt"/>
     </issue>
 
     <issue
diff --git a/buildSrc-tests/src/test/java/androidx/build/clang/CombineObjectFilesTaskTest.kt b/buildSrc-tests/src/test/java/androidx/build/clang/CombineObjectFilesTaskTest.kt
index cb71426..7ea9886 100644
--- a/buildSrc-tests/src/test/java/androidx/build/clang/CombineObjectFilesTaskTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/clang/CombineObjectFilesTaskTest.kt
@@ -64,7 +64,7 @@
                 it.relativeTo(taskOutputDir).path to it.readText()
             }.toList()
         assertThat(outputContents).containsExactly(
-            "linux_x64/libcode.so" to KonanTarget.LINUX_X64.name,
+            "natives/linux_x64/libcode.so" to KonanTarget.LINUX_X64.name,
             "x86/libcode.so" to KonanTarget.ANDROID_X86.name,
             "arm64-v8a/libcode.so" to KonanTarget.ANDROID_ARM64.name
         )
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 51df347..120a7bb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -26,7 +26,6 @@
 import org.gradle.api.Project
 import org.gradle.api.artifacts.type.ArtifactTypeDefinition
 import org.gradle.api.attributes.Attribute
-import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.kotlin.dsl.create
 import org.jetbrains.kotlin.gradle.plugin.CompilerPluginConfig
@@ -122,7 +121,7 @@
          */
         private fun Project.configureForMultiplatform() {
             // This is to allow K/N not matching the kotlinVersion
-            (this.rootProject.property("ext") as ExtraPropertiesExtension).set(
+            this.rootProject.extensions.extraProperties.set(
                 "kotlin.native.version",
                 KOTLIN_NATIVE_VERSION
             )
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 69dde1f..aeab798 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -221,7 +221,7 @@
  * Returns null if there is no alternative project url.
  */
 fun Project.getAlternativeProjectUrl(): String? =
-    project.findProperty(ALTERNATIVE_PROJECT_URL) as? String
+    project.providers.gradleProperty(ALTERNATIVE_PROJECT_URL).getOrNull()
 
 /**
  * Check that version extra meets the specified rules (version is in format major.minor.patch-extra)
@@ -277,18 +277,9 @@
 fun Project.isWriteVersionedApiFilesEnabled(): Boolean =
     findBooleanProperty(WRITE_VERSIONED_API_FILES) ?: true
 
-/** Returns whether the project should generate documentation. */
-fun Project.isDocumentationEnabled(): Boolean {
-    if (System.getenv().containsKey("ANDROIDX_PROJECTS")) {
-        val projects = System.getenv()["ANDROIDX_PROJECTS"] as String
-        if (projects != "ALL") return false
-    }
-    return (project.findProperty(ENABLE_DOCUMENTATION) as? String)?.toBoolean() ?: true
-}
-
 /** Returns whether the build is for checking forward compatibility across projects */
 fun Project.usingMaxDepVersions(): Boolean {
-    return project.hasProperty(USE_MAX_DEP_VERSIONS)
+    return project.providers.gradleProperty(USE_MAX_DEP_VERSIONS).isPresent()
 }
 
 /**
@@ -320,7 +311,7 @@
 fun Project.isCustomCompileSdkAllowed(): Boolean =
     findBooleanProperty(ALLOW_CUSTOM_COMPILE_SDK) ?: true
 
-fun Project.findBooleanProperty(propName: String) = (findProperty(propName) as? String)?.toBoolean()
+fun Project.findBooleanProperty(propName: String) = booleanPropertyProvider(propName).get()
 
 fun Project.booleanPropertyProvider(propName: String): Provider<Boolean> {
     return project.providers.gradleProperty(propName).map { s -> s.toBoolean() }.orElse(false)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
index 4503c7b..4bb9027 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.build.gradle.extraPropertyOrNull
 import androidx.build.gradle.isRoot
 import groovy.xml.DOMBuilder
 import java.net.URI
@@ -195,7 +196,7 @@
             }
 
             private fun Project.requireProperty(name: String): String {
-                return checkNotNull(findProperty(name)) {
+                return checkNotNull(extraPropertyOrNull(name)) {
                         "missing $name property. It must be defined in the gradle.properties file"
                     }
                     .toString()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
index 5759c76d..3d041e1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.build.gradle.extraPropertyOrNull
 import java.io.File
 import org.gradle.api.Project
 import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
@@ -83,7 +84,7 @@
 
     private fun Project.overrideKotlinNativeDependenciesUrlToLocalDirectory() {
         val compilerDaemonDisabled =
-            findProperty(DISABLE_COMPILER_DAEMON_FLAG)?.toString()?.toBoolean() == true
+            extraPropertyOrNull(DISABLE_COMPILER_DAEMON_FLAG)?.toString()?.toBoolean() == true
         val konanPrebuiltsFolder = getKonanPrebuiltsFolder()
         val rootBaseDir = if (compilerDaemonDisabled) projectDir else rootProject.projectDir
         // use relative path so it doesn't affect gradle remote cache.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
index 115050d..874ebf9 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
@@ -105,8 +105,8 @@
     androidXExtension: AndroidXExtension
 ) {
     writeVersionFile.configure {
-        val group = findProperty("group") as String
-        val artifactId = findProperty("name") as String
+        val group = project.getGroup() as String
+        val artifactId = project.getName() as String
         val version =
             if (androidXExtension.shouldPublish()) {
                 version().toString()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/CombineObjectFilesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/CombineObjectFilesTask.kt
index 02841a1..b90f919 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/CombineObjectFilesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/CombineObjectFilesTask.kt
@@ -92,7 +92,6 @@
             Family.LINUX to "linux",
             Family.MINGW to "windows",
             Family.OSX to "osx",
-            Family.IOS to "ios"
         )
 
         private val architectureSuffixes = mapOf(
@@ -100,7 +99,6 @@
             Architecture.ARM64 to "arm64",
             Architecture.X64 to "x64",
             Architecture.X86 to "x86"
-
         )
 
         private fun targetFileFor(
@@ -131,7 +129,7 @@
             val architectureSuffix = architectureSuffixes[konanTarget.architecture] ?: error(
                 "Unsupported architecture ${konanTarget.architecture} for $konanTarget"
             )
-            return "${familyPrefix}_$architectureSuffix"
+            return "natives/${familyPrefix}_$architectureSuffix"
         }
     }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeTargetCompilation.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeTargetCompilation.kt
index 6d4c312..41d3c85 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeTargetCompilation.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeTargetCompilation.kt
@@ -109,8 +109,8 @@
         val relativeHeaderPaths = when (konanTarget.family) {
             Family.MINGW -> {
                 listOf(
-                    "/windows-x86/include",
-                    "/windows-x86/include/win32"
+                    "windows-x86/include",
+                    "windows-x86/include/win32"
                 )
             }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 6307291..4dfd2de 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -110,9 +110,8 @@
             val instance = AffectedModuleDetectorWrapper()
             rootProject.extensions.add(ROOT_PROP_NAME, instance)
 
-            val enabled =
-                rootProject.hasProperty(ENABLE_ARG) &&
-                    rootProject.findProperty(ENABLE_ARG) != "false"
+            val enabledProvider = rootProject.providers.gradleProperty(ENABLE_ARG)
+            val enabled = enabledProvider.isPresent() && enabledProvider.get() != "false"
 
             val distDir = rootProject.getDistributionDirectory()
             val outputFile = distDir.resolve(LOG_FILE_NAME)
@@ -134,10 +133,9 @@
                 instance.wrapped = provider
                 return
             }
-            val baseCommitOverride: String? = rootProject.findProperty(BASE_COMMIT_ARG) as String?
-            if (baseCommitOverride != null) {
-                logger.info("using base commit override $baseCommitOverride")
-            }
+            val baseCommitOverride: Provider<String> =
+                rootProject.providers.gradleProperty(BASE_COMMIT_ARG)
+
             gradle.taskGraph.whenReady {
                 logger.lifecycle("projects evaluated")
                 val projectGraph = ProjectGraph(rootProject)
@@ -257,7 +255,7 @@
         var cobuiltTestPaths: Set<Set<String>>?
         var alwaysBuildIfExists: Set<String>?
         var ignoredPaths: Set<String>?
-        var baseCommitOverride: String?
+        var baseCommitOverride: Provider<String>?
         var gitChangedFilesProvider: Provider<List<String>>
     }
 
@@ -266,10 +264,6 @@
         if (parameters.acceptAll) {
             AcceptAll(null)
         } else {
-            if (parameters.baseCommitOverride != null) {
-                logger.info("using base commit override ${parameters.baseCommitOverride}")
-            }
-
             AffectedModuleDetectorImpl(
                 projectGraph = parameters.projectGraph,
                 dependencyTracker = parameters.dependencyTracker,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index c73c126..b96c9d6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -27,6 +27,7 @@
 import androidx.build.getDistributionDirectory
 import androidx.build.getKeystore
 import androidx.build.getLibraryByName
+import androidx.build.getSupportRootFolder
 import androidx.build.metalava.versionMetadataUsage
 import androidx.build.multiplatformUsage
 import androidx.build.versionCatalog
@@ -521,7 +522,7 @@
                 var taskStartTime: LocalDateTime? = null
                 task.argsJsonFile.set(
                     File(
-                        project.rootProject.getDistributionDirectory(),
+                        project.getDistributionDirectory(),
                         "dackkaArgs-${project.name}.json"
                     )
                 )
@@ -542,7 +543,7 @@
                     dackkaClasspath.from(project.files(dackkaConfiguration))
                     destinationDir.set(generatedDocsDir)
                     frameworkSamplesDir.set(
-                        project.rootProject.layout.projectDirectory.dir("samples")
+                        File(project.getSupportRootFolder(), "samples")
                     )
                     samplesDeprecatedDir.set(unzippedDeprecatedSamplesSources)
                     samplesJvmDir.set(unzippedJvmSamplesSources)
@@ -550,7 +551,7 @@
                     jvmSourcesDir.set(unzippedJvmSourcesDirectory)
                     multiplatformSourcesDir.set(unzippedMultiplatformSourcesDirectory)
                     projectListsDirectory.set(
-                        project.rootProject.layout.projectDirectory.dir("docs-public/package-lists")
+                        File(project.getSupportRootFolder(), "docs-public/package-lists")
                     )
                     dependenciesClasspath.from(
                         dependencyClasspath +
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
index fca8225..b2c1cf6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/gitclient/GitClient.kt
@@ -38,12 +38,12 @@
  * @param baseCommitOverride optional value to use to override last merge commit
  */
 fun Project.getChangedFilesProvider(
-    baseCommitOverride: String?,
+    baseCommitOverride: Provider<String>,
 ): Provider<List<String>> {
     val changeInfoPath = System.getenv("CHANGE_INFO")
     val manifestPath = System.getenv("MANIFEST")
     return if (changeInfoPath != null && manifestPath != null) {
-        if (baseCommitOverride != null) throw GradleException(
+        if (baseCommitOverride.isPresent()) throw GradleException(
             "Overriding base commit is not supported when using CHANGE_INFO and MANIFEST"
         )
         getChangedFilesFromChangeInfoProvider(manifestPath, changeInfoPath)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
index e80fc19..1023659 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -31,7 +31,6 @@
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 import org.gradle.api.internal.tasks.userinput.UserInputHandler
-import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.api.tasks.Internal
 import org.gradle.api.tasks.TaskAction
 import org.gradle.internal.service.ServiceRegistry
@@ -339,7 +338,7 @@
 abstract class PlaygroundStudioTask : RootStudioTask() {
     @get:Internal
     val supportRootFolder =
-        (project.rootProject.property("ext") as ExtraPropertiesExtension).let {
+        (project.rootProject.extensions.extraProperties).let {
             it.get("supportRootFolder") as File
         }
 
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
index 685ccd6..f592083 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
@@ -18,6 +18,7 @@
 
 package androidx.build
 
+import androidx.build.gradle.extraPropertyOrNull
 import java.io.File
 import org.gradle.api.Project
 import org.gradle.api.file.FileCollection
@@ -26,13 +27,17 @@
 abstract class AndroidConfigImpl(private val project: Project) : AndroidConfig {
     override val buildToolsVersion: String = "35.0.0-rc1"
 
-    override val compileSdk: Int by lazy { project.findProperty(COMPILE_SDK).toString().toInt() }
+    override val compileSdk: Int by lazy {
+        val sdkString = project.extraPropertyOrNull(COMPILE_SDK)?.toString()
+        check(sdkString != null) { "$COMPILE_SDK is unset" }
+        sdkString.toInt()
+    }
 
     override val minSdk: Int = 21
     override val ndkVersion: String = "25.2.9519653"
 
     override val targetSdk: Int by lazy {
-        project.findProperty(TARGET_SDK_VERSION).toString().toInt()
+        project.providers.gradleProperty(TARGET_SDK_VERSION).get().toInt()
     }
 
     companion object {
@@ -95,7 +100,7 @@
 }
 
 fun Project.getPrebuiltsRoot(): File {
-    return File(project.rootProject.property("prebuiltsRoot").toString())
+    return File(project.extraPropertyOrNull("prebuiltsRoot").toString())
 }
 
 /** @return the project's Android SDK stub JAR as a File. */
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt b/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
index ab190af..74e1548 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.build.gradle.extraPropertyOrNull
 import java.util.Locale
 import org.gradle.api.Project
 import org.gradle.kotlin.dsl.create
@@ -116,7 +117,9 @@
 /** Extension used to store parsed KMP configuration information. */
 private open class KmpPlatformsExtension(project: Project) {
     val enabledKmpPlatforms =
-        parseTargetPlatformsFlag(project.findProperty(ENABLED_KMP_TARGET_PLATFORMS) as? String)
+        parseTargetPlatformsFlag(
+            project.extraPropertyOrNull(ENABLED_KMP_TARGET_PLATFORMS) as? String
+        )
 }
 
 fun Project.enableJs(): Boolean = enabledKmpPlatforms.contains(PlatformGroup.JS)
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/ProjectLayoutType.kt b/buildSrc/public/src/main/kotlin/androidx/build/ProjectLayoutType.kt
index ec0129f..aedc32f 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/ProjectLayoutType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/ProjectLayoutType.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.build.gradle.extraPropertyOrNull
 import org.gradle.api.Project
 
 enum class ProjectLayoutType {
@@ -26,7 +27,7 @@
         /** Returns the project layout type for the project (PLAYGROUND or ANDROIDX) */
         @JvmStatic
         fun from(project: Project): ProjectLayoutType {
-            val value = project.findProperty(STUDIO_TYPE)?.toString()
+            val value = project.extraPropertyOrNull(STUDIO_TYPE)
             return when (value) {
                 "playground" -> ProjectLayoutType.PLAYGROUND
                 null,
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
index 9b9c338..2fa6bde 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
@@ -21,7 +21,6 @@
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 import org.gradle.api.file.FileTree
-import org.gradle.api.plugins.ExtraPropertiesExtension
 
 /** Writes the appropriate SDK path to local.properties file in specified location. */
 fun Project.writeSdkPathToLocalPropertiesFile() {
@@ -50,7 +49,7 @@
 /** Returns the root project's platform-specific SDK path as a file. */
 fun Project.getSdkPath(): File {
     if (
-        rootProject.plugins.hasPlugin("AndroidXPlaygroundRootPlugin") ||
+        ProjectLayoutType.from(project) == ProjectLayoutType.PLAYGROUND ||
             System.getenv("COMPOSE_DESKTOP_GITHUB_BUILD") != null
     ) {
         // This is not full checkout, use local settings instead.
@@ -99,7 +98,7 @@
 
 /** Sets the path to the canonical root project directory, e.g. {@code frameworks/support}. */
 fun Project.setSupportRootFolder(rootDir: File?) {
-    val extension = project.property("ext") as ExtraPropertiesExtension
+    val extension = project.extensions.extraProperties
     return extension.set("supportRootFolder", rootDir)
 }
 
@@ -110,7 +109,7 @@
  * because it is generalized to also work for the "ui" project and playground projects.
  */
 fun Project.getSupportRootFolder(): File {
-    val extension = project.property("ext") as ExtraPropertiesExtension
+    val extension = project.extensions.extraProperties
     return extension.get("supportRootFolder") as File
 }
 
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/gradle/Extensions.kt b/buildSrc/public/src/main/kotlin/androidx/build/gradle/Extensions.kt
index 7d2ebd4..92bbc57 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/gradle/Extensions.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/gradle/Extensions.kt
@@ -20,3 +20,18 @@
 
 val Project.isRoot
     get() = this == rootProject
+
+/**
+ * Implements project.extensions.extraProperties.getOrNull(key)
+ * TODO(https://github.com/gradle/gradle/issues/28857) use simpler replacement when available
+ *
+ * Note that providers.gradleProperty() might return null in cases where this function can
+ * find a value: https://github.com/gradle/gradle/issues/23572
+ */
+fun Project.extraPropertyOrNull(key: String): Any? {
+    val container = project.extensions.extraProperties
+    var result: Any? = null
+    if (container.has(key))
+        result = container.get(key)
+    return result
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 18fbada..cbbe2a7 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -61,6 +61,8 @@
 import androidx.camera.testing.impl.mocks.helpers.CallTimesAtLeast
 import androidx.camera.video.Recorder.VIDEO_CAPABILITIES_SOURCE_CAMCORDER_PROFILE
 import androidx.camera.video.Recorder.VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES
+import androidx.camera.video.Recorder.sRetrySetupVideoDelayMs
+import androidx.camera.video.Recorder.sRetrySetupVideoMaxCount
 import androidx.camera.video.VideoOutput.SourceState.ACTIVE_NON_STREAMING
 import androidx.camera.video.VideoOutput.SourceState.ACTIVE_STREAMING
 import androidx.camera.video.VideoOutput.SourceState.INACTIVE
@@ -962,16 +964,10 @@
     fun canRecoverFromErrorState(): Unit = runBlocking {
         // Arrange.
         // Create a video encoder factory that will fail on first 2 create encoder requests.
-        var createEncoderRequestCount = 0
         val recorder = createRecorder(
-            videoEncoderFactory = { executor, config ->
-                if (createEncoderRequestCount < 2) {
-                    createEncoderRequestCount++
-                    throw InvalidConfigException("Create video encoder fail on purpose.")
-                } else {
-                    Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
-                }
-            })
+            videoEncoderFactory = createVideoEncoderFactory(failCreationTimes = 2),
+            retrySetupVideoMaxCount = 0, // Don't retry
+        )
         // Recorder initialization should fail by 1st encoder creation fail.
         // Wait STREAM_ID_ERROR which indicates Recorder enter the error state.
         withTimeoutOrNull(3000) {
@@ -992,6 +988,25 @@
     }
 
     @Test
+    fun canRetrySetupVideo(): Unit = runBlocking {
+        // Arrange.
+        // Create a video encoder factory that will fail on first 2 create encoder requests.
+        val recorder = createRecorder(
+            videoEncoderFactory = createVideoEncoderFactory(failCreationTimes = 2),
+            retrySetupVideoMaxCount = 3,
+            retrySetupVideoDelayMs = 10, // make test quicker
+            )
+
+        // Act and verify.
+        val recording = createRecordingProcess(recorder = recorder)
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_NONE)
+        }
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = 31)
     fun audioRecordIsAttributed() = runBlocking {
         // Arrange.
@@ -1094,6 +1109,8 @@
         videoEncoderFactory: EncoderFactory? = null,
         audioEncoderFactory: EncoderFactory? = null,
         targetBitrate: Int? = null,
+        retrySetupVideoMaxCount: Int? = null,
+        retrySetupVideoDelayMs: Long? = null,
     ): Recorder {
         val recorder = Recorder.Builder().apply {
             qualitySelector?.let { setQualitySelector(it) }
@@ -1102,7 +1119,10 @@
             videoEncoderFactory?.let { setVideoEncoderFactory(it) }
             audioEncoderFactory?.let { setAudioEncoderFactory(it) }
             targetBitrate?.let { setTargetVideoEncodingBitRate(targetBitrate) }
-        }.build()
+        }.build().apply {
+            retrySetupVideoMaxCount?.let { sRetrySetupVideoMaxCount = it }
+            retrySetupVideoDelayMs?.let { sRetrySetupVideoDelayMs = it }
+        }
         if (sendSurfaceRequest) {
             recorder.sendSurfaceRequest()
         }
@@ -1295,6 +1315,19 @@
         }
     }
 
+    @Suppress("SameParameterValue")
+    private fun createVideoEncoderFactory(failCreationTimes: Int = 0): EncoderFactory {
+        var createEncoderRequestCount = 0
+        return EncoderFactory { executor, config ->
+            if (createEncoderRequestCount < failCreationTimes) {
+                createEncoderRequestCount++
+                throw InvalidConfigException("Create video encoder fail on purpose.")
+            } else {
+                Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
+            }
+        }
+    }
+
     // It fails on devices with certain chipset if the codec is stopped when the camera is still
     // producing frames to the provided surface. This method first stop the camera from
     // producing frames then stops the recording safely on the problematic devices.
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index f05dfd2..f9d1af9 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -342,11 +342,18 @@
     // The audio data is expected to be less than 1 kB, the value of the cache size is used to limit
     // the memory used within an acceptable range.
     private static final int AUDIO_CACHE_SIZE = 60;
+    private static final int RETRY_SETUP_VIDEO_MAX_COUNT = 3;
+    private static final long RETRY_SETUP_VIDEO_DELAY_MS = 1000L;
     @VisibleForTesting
     static final EncoderFactory DEFAULT_ENCODER_FACTORY = EncoderImpl::new;
     private static final Executor AUDIO_EXECUTOR =
             CameraXExecutors.newSequentialExecutor(CameraXExecutors.ioExecutor());
 
+    @VisibleForTesting
+    static int sRetrySetupVideoMaxCount = RETRY_SETUP_VIDEO_MAX_COUNT;
+    @VisibleForTesting
+    static long sRetrySetupVideoDelayMs = RETRY_SETUP_VIDEO_DELAY_MS;
+
     private final MutableStateObservable<StreamInfo> mStreamInfo;
     // Used only by getExecutor()
     private final Executor mUserProvidedExecutor;
@@ -486,6 +493,8 @@
     VideoEncoderSession mVideoEncoderSessionToRelease = null;
     double mAudioAmplitude = 0;
     private boolean mShouldSendResumeEvent = false;
+    @Nullable
+    private SetupVideoTask mSetupVideoTask = null;
     //--------------------------------------------------------------------------------------------//
 
     Recorder(@Nullable Executor executor, @NonNull MediaSpec mediaSpec,
@@ -798,7 +807,8 @@
                                             "surface request is required to retry "
                                                     + "initialization.");
                                 }
-                                configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase);
+                                configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase,
+                                        false);
                             });
                         } else {
                             setState(State.PENDING_RECORDING);
@@ -1024,7 +1034,7 @@
         if (mLatestSurfaceRequest != null && !mLatestSurfaceRequest.isServiced()) {
             mLatestSurfaceRequest.willNotProvideSurface();
         }
-        configureInternal(mLatestSurfaceRequest = request, mVideoSourceTimebase = timebase);
+        configureInternal(mLatestSurfaceRequest = request, mVideoSourceTimebase = timebase, true);
     }
 
     @ExecutedBy("mSequentialExecutor")
@@ -1140,7 +1150,7 @@
 
     @ExecutedBy("mSequentialExecutor")
     private void configureInternal(@NonNull SurfaceRequest surfaceRequest,
-            @NonNull Timebase videoSourceTimebase) {
+            @NonNull Timebase videoSourceTimebase, boolean enableRetrySetupVideo) {
         if (surfaceRequest.isServiced()) {
             Logger.w(TAG, "Ignore the SurfaceRequest since it is already served.");
             return;
@@ -1164,50 +1174,120 @@
                         + "produce EncoderProfiles  for advertised quality.");
             }
         }
-        setupVideo(surfaceRequest, videoSourceTimebase);
+        if (mSetupVideoTask != null) {
+            mSetupVideoTask.cancel();
+        }
+        mSetupVideoTask = new SetupVideoTask(surfaceRequest, videoSourceTimebase,
+                enableRetrySetupVideo ? sRetrySetupVideoMaxCount : 0);
+        mSetupVideoTask.start();
     }
 
-    @SuppressWarnings("ObjectToString")
-    @ExecutedBy("mSequentialExecutor")
-    private void setupVideo(@NonNull SurfaceRequest request, @NonNull Timebase timebase) {
-        safeToCloseVideoEncoder().addListener(() -> {
-            if (request.isServiced()
-                    || (mVideoEncoderSession.isConfiguredSurfaceRequest(request)
-                    && !isPersistentRecordingInProgress())) {
-                // Ignore the surface request if it's already serviced. Or the video encoder
-                // session is already configured, unless there's a persistent recording is running.
-                Logger.w(TAG, "Ignore the SurfaceRequest " + request + " isServiced: "
-                        + request.isServiced() + " VideoEncoderSession: " + mVideoEncoderSession
-                        + " has been configured with a persistent in-progress recording.");
+    /** A class for setting up video encoder with a retry mechanism. */
+    private class SetupVideoTask {
+        private final SurfaceRequest mSurfaceRequest;
+        private final Timebase mTimebase;
+        private final int mMaxRetryCount;
+
+        private boolean mIsComplete = false;
+        private int mRetryCount = 0;
+        @Nullable
+        private ScheduledFuture<?> mRetryFuture = null;
+
+        SetupVideoTask(@NonNull SurfaceRequest surfaceRequest, @NonNull Timebase timebase,
+                int maxRetryCount) {
+            mSurfaceRequest = surfaceRequest;
+            mTimebase = timebase;
+            mMaxRetryCount = maxRetryCount;
+        }
+
+        @ExecutedBy("mSequentialExecutor")
+        void start() {
+            if (mIsComplete) {
+                Logger.w(TAG, "Task has been completed before start");
                 return;
             }
-            VideoEncoderSession videoEncoderSession =
-                    new VideoEncoderSession(mVideoEncoderFactory, mSequentialExecutor, mExecutor);
-            MediaSpec mediaSpec = getObservableData(mMediaSpec);
-            ListenableFuture<Encoder> configureFuture =
-                    videoEncoderSession.configure(request, timebase, mediaSpec,
-                            mResolvedEncoderProfiles);
-            mVideoEncoderSession = videoEncoderSession;
-            Futures.addCallback(configureFuture, new FutureCallback<Encoder>() {
-                @Override
-                public void onSuccess(@Nullable Encoder result) {
-                    Logger.d(TAG, "VideoEncoder is created. " + result);
-                    if (result == null) {
-                        return;
-                    }
-                    Preconditions.checkState(mVideoEncoderSession == videoEncoderSession);
-                    Preconditions.checkState(mVideoEncoder == null);
-                    onVideoEncoderReady(videoEncoderSession);
-                    onConfigured();
-                }
+            setupVideo(mSurfaceRequest, mTimebase);
+        }
 
-                @Override
-                public void onFailure(@NonNull Throwable t) {
-                    Logger.d(TAG, "VideoEncoder Setup error: " + t);
-                    onEncoderSetupError(t);
+        @ExecutedBy("mSequentialExecutor")
+        void cancel() {
+            if (mIsComplete) {
+                return;
+            }
+            mIsComplete = true;
+            if (mRetryFuture != null) {
+                mRetryFuture.cancel(false);
+                mRetryFuture = null;
+            }
+        }
+
+        @SuppressWarnings("ObjectToString")
+        @ExecutedBy("mSequentialExecutor")
+        private void setupVideo(@NonNull SurfaceRequest request, @NonNull Timebase timebase) {
+            safeToCloseVideoEncoder().addListener(() -> {
+                if (request.isServiced() || mIsComplete
+                        || (mVideoEncoderSession.isConfiguredSurfaceRequest(request)
+                        && !isPersistentRecordingInProgress())) {
+                    // Ignore the surface request if it's already serviced. Or the video encoder
+                    // session is already configured, unless there's a persistent recording is
+                    // running. Or the task has been completed.
+                    Logger.w(TAG, "Ignore the SurfaceRequest " + request + " isServiced: "
+                            + request.isServiced() + " is setup video complete: " + mIsComplete
+                            + " VideoEncoderSession: " + mVideoEncoderSession
+                            + " has been configured with a persistent in-progress recording.");
+                    mIsComplete = true;
+                    return;
                 }
+                VideoEncoderSession videoEncoderSession =
+                        new VideoEncoderSession(mVideoEncoderFactory, mSequentialExecutor,
+                                mExecutor);
+                MediaSpec mediaSpec = getObservableData(mMediaSpec);
+                ListenableFuture<Encoder> configureFuture =
+                        videoEncoderSession.configure(request, timebase, mediaSpec,
+                                mResolvedEncoderProfiles);
+                mVideoEncoderSession = videoEncoderSession;
+                Futures.addCallback(configureFuture, new FutureCallback<Encoder>() {
+                    @Override
+                    public void onSuccess(@Nullable Encoder result) {
+                        Logger.d(TAG, "VideoEncoder is created. " + result);
+                        if (mIsComplete) {
+                            if (result != null) {
+                                result.release();
+                            }
+                            return;
+                        }
+                        mIsComplete = true;
+                        if (result == null) {
+                            return;
+                        }
+                        Preconditions.checkState(mVideoEncoderSession == videoEncoderSession);
+                        Preconditions.checkState(mVideoEncoder == null);
+                        onVideoEncoderReady(videoEncoderSession);
+                        onConfigured();
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Logger.w(TAG, "VideoEncoder Setup error: " + t, t);
+                        if (mIsComplete) {
+                            return;
+                        }
+                        if (mRetryCount < mMaxRetryCount) {
+                            mRetryCount++;
+                            mRetryFuture = scheduleTask(() -> {
+                                if (!mIsComplete) {
+                                    Logger.d(TAG, "Retry setupVideo #" + mRetryCount);
+                                    setupVideo(mSurfaceRequest, mTimebase);
+                                }
+                            }, mSequentialExecutor, sRetrySetupVideoDelayMs, TimeUnit.MILLISECONDS);
+                        } else {
+                            mIsComplete = true;
+                            onEncoderSetupError(t);
+                        }
+                    }
+                }, mSequentialExecutor);
             }, mSequentialExecutor);
-        }, mSequentialExecutor);
+        }
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -2099,20 +2179,19 @@
                 // devices that require it and to act as a flag that we need to signal the source
                 // stopped.
                 Encoder finalVideoEncoder = mVideoEncoder;
-                mSourceNonStreamingTimeout = CameraXExecutors.mainThreadExecutor().schedule(
-                        () -> mSequentialExecutor.execute(() -> {
-                            Logger.d(TAG, "The source didn't become non-streaming "
-                                    + "before timeout. Waited " + SOURCE_NON_STREAMING_TIMEOUT_MS
-                                    + "ms");
-                            if (DeviceQuirks.get(
-                                    DeactivateEncoderSurfaceBeforeStopEncoderQuirk.class)
-                                    != null) {
-                                // Even in the case of timeout, we tell the encoder the source has
-                                // stopped because devices with this quirk require that the codec
-                                // produce a new surface.
-                                notifyEncoderSourceStopped(finalVideoEncoder);
-                            }
-                        }), SOURCE_NON_STREAMING_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                mSourceNonStreamingTimeout = scheduleTask(() -> {
+                    Logger.d(TAG, "The source didn't become non-streaming "
+                            + "before timeout. Waited " + SOURCE_NON_STREAMING_TIMEOUT_MS
+                            + "ms");
+                    if (DeviceQuirks.get(
+                            DeactivateEncoderSurfaceBeforeStopEncoderQuirk.class)
+                            != null) {
+                        // Even in the case of timeout, we tell the encoder the source has
+                        // stopped because devices with this quirk require that the codec
+                        // produce a new surface.
+                        notifyEncoderSourceStopped(finalVideoEncoder);
+                    }
+                }, mSequentialExecutor, SOURCE_NON_STREAMING_TIMEOUT_MS, TimeUnit.MILLISECONDS);
             } else {
                 // Source is already non-streaming. Signal source is stopped right away.
                 notifyEncoderSourceStopped(mVideoEncoder);
@@ -2225,12 +2304,16 @@
         // If the latest surface request hasn't been serviced, use it to re-configure the Recorder.
         if (shouldConfigure && mLatestSurfaceRequest != null
                 && !mLatestSurfaceRequest.isServiced()) {
-            configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase);
+            configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase, false);
         }
     }
 
     @ExecutedBy("mSequentialExecutor")
     private void resetVideo() {
+        if (mSetupVideoTask != null) {
+            mSetupVideoTask.cancel();
+            mSetupVideoTask = null;
+        }
         if (mVideoEncoder != null) {
             Logger.d(TAG, "Releasing video encoder.");
             tryReleaseVideoEncoder();
@@ -2451,7 +2534,7 @@
 
         // Perform required actions from state changes inline on sequential executor but unlocked.
         if (needsConfigure) {
-            configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase);
+            configureInternal(mLatestSurfaceRequest, mVideoSourceTimebase, false);
         } else if (needsReset) {
             reset();
         } else if (recordingToStart != null) {
@@ -2780,6 +2863,13 @@
         mAudioState = audioState;
     }
 
+    @NonNull
+    private static ScheduledFuture<?> scheduleTask(@NonNull Runnable task,
+            @NonNull Executor executor, long delay, TimeUnit timeUnit) {
+        return CameraXExecutors.mainThreadExecutor().schedule(() -> executor.execute(task), delay,
+                timeUnit);
+    }
+
     private static int supportedMuxerFormatOrDefaultFrom(
             @Nullable VideoValidatedEncoderProfilesProxy profilesProxy, int defaultMuxerFormat) {
         if (profilesProxy != null) {
diff --git a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/ui/widget/Button.kt b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/ui/widget/Button.kt
index ad474a8..cf4ffa0 100644
--- a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/ui/widget/Button.kt
+++ b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/ui/widget/Button.kt
@@ -46,7 +46,7 @@
     content: @Composable () -> Unit
 ) {
     CompositionLocalProvider(
-        LocalRippleConfiguration provides RippleConfiguration(isEnabled = enabled)
+        LocalRippleConfiguration provides if (enabled) RippleConfiguration() else null
     ) {
         FloatingActionButton(
              (enabled) onClick else { {} },
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
index 5c53be8..c4f2220 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
@@ -200,9 +200,6 @@
                     listOf(
                         VideoCapture.Builder(Recorder.Builder().build())
                             .setTargetFrameRate(targetFpsRange),
-                        // TODO: b/333365764 - Remove extra Preview use case added to avoid capture
-                        //  session failure due to MediaCodec error
-                        Preview.Builder()
                     )
                 )
 
diff --git a/car/app/app/gradle.properties b/car/app/app/gradle.properties
deleted file mode 100644
index a060082..0000000
--- a/car/app/app/gradle.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-androidx.targetSdkVersion = 33
diff --git a/car/app/app/lint-baseline.xml b/car/app/app/lint-baseline.xml
index 16305d3..e10674d 100644
--- a/car/app/app/lint-baseline.xml
+++ b/car/app/app/lint-baseline.xml
@@ -362,6 +362,15 @@
     </issue>
 
     <issue
+        id="UnspecifiedRegisterReceiverFlag"
+        message="`mBroadcastReceiver` is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected broadcasts registered for androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"
+        errorLine1="            mContext.registerReceiver(mBroadcastReceiver, filter);"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/connection/CarConnectionTypeLiveData.java"/>
+    </issue>
+
+    <issue
         id="UnsafeOptInUsageError"
         message="This declaration is opt-in and its usage should be marked with `@androidx.car.app.annotations.ExperimentalCarApi` or `@OptIn(markerClass = androidx.car.app.annotations.ExperimentalCarApi.class)`"
         errorLine1="    private final List&lt;CarZone> mCarZones;"
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
index 8092881..fbaa3a4 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
@@ -16,6 +16,8 @@
 
 package androidx.car.app.model;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.SuppressLint;
@@ -24,6 +26,7 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 import androidx.car.app.annotations.CarProtocol;
 import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.annotations.KeepFields;
@@ -252,6 +255,15 @@
             return this;
         }
 
+        /** @see #setOnItemsVisibilityChangedListener(OnItemVisibilityChangedListener) */
+        @NonNull
+        @RestrictTo(LIBRARY)
+        public Builder setOnItemsVisibilityChangedDelegate(
+                @Nullable OnItemVisibilityChangedDelegate onItemVisibilityChangedDelegate) {
+            mOnItemVisibilityChangedDelegate = onItemVisibilityChangedDelegate;
+            return this;
+        }
+
         /**
          * Marks the list as selectable by setting the {@link OnSelectedListener} to call when an
          * item is selected by the user, or set to {@code null} to mark the list as non-selectable.
@@ -276,6 +288,15 @@
             return this;
         }
 
+        /** @see #setOnSelectedListener(OnSelectedListener)  */
+        @NonNull
+        @RestrictTo(LIBRARY)
+        public Builder setOnSelectedDelegate(@Nullable OnSelectedDelegate onSelectedDelegate) {
+            mOnSelectedDelegate = onSelectedDelegate;
+            return this;
+        }
+
+
         /**
          * Sets the index of the item to show as selected.
          *
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java b/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java
index 68fbc6b..518d150 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java
@@ -45,6 +45,14 @@
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 public class ItemListTest {
+    private static final Row row1 = new Row.Builder().setTitle("Row1").build();
+    private static final Row row2 = new Row.Builder().setTitle("Row2").build();
+    private static final GridItem gridItem1 = new GridItem.Builder().setTitle("title 1").setImage(BACK).build();
+    private static final GridItem gridItem2 = new GridItem.Builder().setTitle("title 2").setImage(BACK).build();
+    private final OnSelectedListener mockSelectedListener = mock(OnSelectedListener.class);
+    private final OnItemVisibilityChangedListener mockItemVisibilityChangedListener = mock(OnItemVisibilityChangedListener.class);
+    OnDoneCallback mockOnDoneCallback = mock(OnDoneCallback.class);
+
     @Test
     public void createEmpty() {
         ItemList list = new ItemList.Builder().build();
@@ -53,19 +61,13 @@
 
     @Test
     public void createRows() {
-        Row row1 = new Row.Builder().setTitle("Row1").build();
-        Row row2 = new Row.Builder().setTitle("Row2").build();
         ItemList list = new ItemList.Builder().addItem(row1).addItem(row2).build();
 
-        assertThat(list.getItems()).hasSize(2);
-        assertThat(list.getItems().get(0)).isEqualTo(row1);
-        assertThat(list.getItems().get(1)).isEqualTo(row2);
+        assertThat(list.getItems()).containsExactly(row1, row2).inOrder();
     }
 
     @Test
     public void createGridItems() {
-        GridItem gridItem1 = new GridItem.Builder().setTitle("title 1").setImage(BACK).build();
-        GridItem gridItem2 = new GridItem.Builder().setTitle("title 2").setImage(BACK).build();
         ItemList list = new ItemList.Builder().addItem(gridItem1).addItem(gridItem2).build();
 
         assertThat(list.getItems()).containsExactly(gridItem1, gridItem2).inOrder();
@@ -73,54 +75,45 @@
 
     @Test
     public void clearItems() {
-        Row row1 = new Row.Builder().setTitle("Row1").build();
-        Row row2 = new Row.Builder().setTitle("Row2").build();
         ItemList list = new ItemList.Builder()
                 .addItem(row1)
                 .clearItems()
                 .addItem(row2)
                 .build();
 
-        assertThat(list.getItems()).hasSize(1);
-        assertThat(list.getItems().get(0)).isEqualTo(row2);
+        assertThat(list.getItems()).containsExactly(row2).inOrder();
     }
 
     @Test
     public void setSelectedable_emptyList_throws() {
         assertThrows(
                 IllegalStateException.class,
-                () -> new ItemList.Builder().setOnSelectedListener(selectedIndex -> {
-                }).build());
+                () -> new ItemList.Builder().setOnSelectedListener(mockSelectedListener).build());
     }
 
     @Test
     public void setSelectedIndex_greaterThanListSize_throws() {
-        Row row1 = new Row.Builder().setTitle("Row1").build();
         assertThrows(
                 IllegalStateException.class,
                 () -> new ItemList.Builder()
                         .addItem(row1)
-                        .setOnSelectedListener(selectedIndex -> {
-                        })
+                        .setOnSelectedListener(mockSelectedListener)
                         .setSelectedIndex(2)
                         .build());
     }
 
     @Test
     public void setSelectable() throws RemoteException {
-        OnSelectedListener mockListener = mock(OnSelectedListener.class);
         ItemList itemList =
                 new ItemList.Builder()
-                        .addItem(new Row.Builder().setTitle("title").build())
-                        .setOnSelectedListener(mockListener)
+                        .addItem(row1)
+                        .setOnSelectedListener(mockSelectedListener)
                         .build();
 
-        OnDoneCallback >
 
-
-        itemList.getOnSelectedDelegate().sendSelected(0, onDoneCallback);
-        verify(mockListener).onSelected(eq(0));
-        verify(onDoneCallback).onSuccess(null);
+        itemList.getOnSelectedDelegate().sendSelected(0, mockOnDoneCallback);
+        verify(mockSelectedListener).onSelected(eq(0));
+        verify(mockOnDoneCallback).onSuccess(null);
     }
 
     @Test
@@ -130,15 +123,13 @@
                 () -> new ItemList.Builder()
                         .addItem(new Row.Builder().setTitle("foo").setOnClickListener(() -> {
                         }).build())
-                        .setOnSelectedListener((index) -> {
-                        })
+                        .setOnSelectedListener(mockSelectedListener)
                         .build());
 
         // Positive test.
         new ItemList.Builder()
                 .addItem(new Row.Builder().setTitle("foo").build())
-                .setOnSelectedListener((index) -> {
-                })
+                .setOnSelectedListener(mockSelectedListener)
                 .build();
     }
 
@@ -149,72 +140,65 @@
                 () -> new ItemList.Builder()
                         .addItem(new Row.Builder().setToggle(new Toggle.Builder(isChecked -> {
                         }).build()).build())
-                        .setOnSelectedListener((index) -> {
-                        })
+                        .setOnSelectedListener(mockSelectedListener)
                         .build());
     }
 
     @Test
     public void setOnItemVisibilityChangeListener_triggerListener() {
-        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
         ItemList list =
                 new ItemList.Builder()
-                        .addItem(new Row.Builder().setTitle("1").build())
-                        .setOnItemsVisibilityChangedListener(listener)
+                        .addItem(row1)
+                        .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
                         .build();
 
-        OnDoneCallback >
         list.getOnItemVisibilityChangedDelegate().sendItemVisibilityChanged(0, 1,
-                onDoneCallback);
+                mockOnDoneCallback);
         ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
+        verify(mockItemVisibilityChangedListener).onItemVisibilityChanged(startIndexCaptor.capture(),
                 endIndexCaptor.capture());
-        verify(onDoneCallback).onSuccess(null);
+        verify(mockOnDoneCallback).onSuccess(null);
         assertThat(startIndexCaptor.getValue()).isEqualTo(0);
         assertThat(endIndexCaptor.getValue()).isEqualTo(1);
     }
 
     @Test
     public void setOnItemVisibilityChangeListener_triggerListenerWithFailure() {
-        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
         ItemList list =
                 new ItemList.Builder()
-                        .addItem(new Row.Builder().setTitle("1").build())
-                        .setOnItemsVisibilityChangedListener(listener)
+                        .addItem(row1)
+                        .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
                         .build();
 
         String testExceptionMessage = "Test exception";
-        doThrow(new RuntimeException(testExceptionMessage)).when(listener).onItemVisibilityChanged(
+        doThrow(new RuntimeException(testExceptionMessage)).when(mockItemVisibilityChangedListener).onItemVisibilityChanged(
                 0, 1);
 
-        OnDoneCallback >
         try {
             list.getOnItemVisibilityChangedDelegate().sendItemVisibilityChanged(0, 1,
-                    onDoneCallback);
+                    mockOnDoneCallback);
         } catch (RuntimeException e) {
             assertThat(e.getMessage()).contains(testExceptionMessage);
         }
 
         ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
+        verify(mockItemVisibilityChangedListener).onItemVisibilityChanged(startIndexCaptor.capture(),
                 endIndexCaptor.capture());
-        verify(onDoneCallback).onFailure(any());
+        verify(mockOnDoneCallback).onFailure(any());
         assertThat(startIndexCaptor.getValue()).isEqualTo(0);
         assertThat(endIndexCaptor.getValue()).isEqualTo(1);
     }
 
     @Test
     public void equals_itemListWithRows() {
-        assertThat(createFullyPopulatedRowItemList())
-                .isEqualTo(createFullyPopulatedRowItemList());
+        assertThat(createFullyPopulatedRowItemList()).isEqualTo(createFullyPopulatedRowItemList());
     }
 
     @Test
     public void equals_itemListWithGridItems() {
-        assertThat(createFullyPopulatedGridItemList())
-                .isEqualTo(createFullyPopulatedGridItemList());
+        assertThat(createFullyPopulatedGridItemList()).isEqualTo(createFullyPopulatedGridItemList());
     }
 
     @Test
@@ -225,48 +209,46 @@
 
     @Test
     public void notEquals_differentSelectedIndex() {
-        Row row = new Row.Builder().setTitle("Title").build();
-        ItemList itemList =
-                new ItemList.Builder().setOnSelectedListener((index) -> {
-                }).addItem(row).addItem(row).build();
+        ItemList itemList = new ItemList.Builder()
+                .setOnSelectedListener(mockSelectedListener)
+                .addItem(row1)
+                .addItem(row2)
+                .build();
         assertThat(itemList)
                 .isNotEqualTo(
                         new ItemList.Builder()
-                                .setOnSelectedListener((index) -> {
-                                })
+                                .setOnSelectedListener(mockSelectedListener)
                                 .setSelectedIndex(1)
-                                .addItem(row)
-                                .addItem(row)
+                                .addItem(row1)
+                                .addItem(row2)
                                 .build());
     }
 
     @Test
     public void notEquals_missingSelectedListener() {
-        Row row = new Row.Builder().setTitle("Title").build();
-        ItemList itemList =
-                new ItemList.Builder().setOnSelectedListener((index) -> {
-                }).addItem(row).addItem(row).build();
-        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row).addItem(row).build());
+        ItemList itemList = new ItemList.Builder()
+                .setOnSelectedListener(mockSelectedListener)
+                .addItem(row1)
+                .addItem(row2)
+                .build();
+        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row1).addItem(row2).build());
     }
 
     @Test
     public void notEquals_missingVisibilityChangedListener() {
-        Row row = new Row.Builder().setTitle("Title").build();
         ItemList itemList =
                 new ItemList.Builder()
-                        .setOnItemsVisibilityChangedListener((start, end) -> {
-                        })
-                        .addItem(row)
-                        .addItem(row)
+                        .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
+                        .addItem(row1)
+                        .addItem(row2)
                         .build();
-        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row).addItem(row).build());
+        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row1).addItem(row2).build());
     }
 
     @Test
     public void notEquals_differentRows() {
-        Row row = new Row.Builder().setTitle("Title").build();
-        ItemList itemList = new ItemList.Builder().addItem(row).addItem(row).build();
-        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row).build());
+        ItemList itemList = new ItemList.Builder().addItem(row1).addItem(row1).build();
+        assertThat(itemList).isNotEqualTo(new ItemList.Builder().addItem(row1).build());
     }
 
     @Test
@@ -277,6 +259,20 @@
     }
 
     @Test
+    public void equals_delegateVsCallback() {
+        ItemList itemListWithListeners = new ItemList.Builder().addItem(row1)
+                .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
+                .setOnSelectedListener(mockSelectedListener)
+                .build();
+        ItemList itemListWithDelegates = new ItemList.Builder().addItem(row1)
+                .setOnItemsVisibilityChangedDelegate(OnItemVisibilityChangedDelegateImpl.create(mockItemVisibilityChangedListener))
+                .setOnSelectedDelegate(OnSelectedDelegateImpl.create(mockSelectedListener))
+                .build();
+
+        assertThat(itemListWithListeners).isEqualTo(itemListWithDelegates);
+    }
+
+    @Test
     public void toBuilder_createsEquivalentInstance_rows() {
         ItemList itemList = createFullyPopulatedRowItemList();
 
@@ -294,51 +290,41 @@
     public void toBuilder_fieldsCanBeOverwritten() {
         Row row = new Row.Builder().setTitle("Title").build();
         ItemList itemList = new ItemList.Builder()
-                .setOnSelectedListener((index) -> {
-                })
+                .setOnSelectedListener(mockSelectedListener)
                 .setNoItemsMessage("no items")
                 .setSelectedIndex(0)
-                .setOnItemsVisibilityChangedListener((start, end) -> {
-                })
+                .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
                 .addItem(row)
                 .build();
 
         // Verify fields can be overwritten (no crash)
         itemList.toBuilder()
-                .setOnSelectedListener((index) -> {
-                })
+                .setOnSelectedListener(mockSelectedListener)
                 .setNoItemsMessage("no items")
                 .setSelectedIndex(0)
-                .setOnItemsVisibilityChangedListener((start, end) -> {
-                })
+                .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
                 .clearItems()
                 .addItem(row)
                 .build();
     }
 
-    private static ItemList createFullyPopulatedRowItemList() {
-        Row row = new Row.Builder().setTitle("Title").build();
+    private ItemList createFullyPopulatedRowItemList() {
         return new ItemList.Builder()
-                .setOnSelectedListener((index) -> {
-                })
+                .setOnSelectedListener(mockSelectedListener)
                 .setNoItemsMessage("no items")
                 .setSelectedIndex(0)
-                .setOnItemsVisibilityChangedListener((start, end) -> {
-                })
-                .addItem(row)
+                .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
+                .addItem(row1)
                 .build();
     }
 
-    private static ItemList createFullyPopulatedGridItemList() {
-        GridItem gridItem = new GridItem.Builder().setImage(BACK).setTitle("Title").build();
+    private ItemList createFullyPopulatedGridItemList() {
         return new ItemList.Builder()
-                .setOnSelectedListener((index) -> {
-                })
+                .setOnSelectedListener(mockSelectedListener)
                 .setNoItemsMessage("no items")
                 .setSelectedIndex(0)
-                .setOnItemsVisibilityChangedListener((start, end) -> {
-                })
-                .addItem(gridItem)
+                .setOnItemsVisibilityChangedListener(mockItemVisibilityChangedListener)
+                .addItem(gridItem1)
                 .build();
     }
 }
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 111f3e9..fc75648 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -206,6 +206,12 @@
     it.onTotalDurationChanged()
 }
 
+private val SeekableStateObserver: SnapshotStateObserver by lazy(LazyThreadSafetyMode.NONE) {
+    SnapshotStateObserver { it() }.apply {
+        start()
+    }
+}
+
 /**
  * A [TransitionState] that can manipulate the progress of the [Transition] by seeking
  * with [seekTo] or animating with [animateTo].
@@ -234,8 +240,6 @@
      */
     private var transition: Transition<S>? = null
 
-    private val observer = SnapshotStateObserver { it() }
-
     // Used for seekToFraction calculations to avoid allocation
     internal var totalDurationNanos = 0L
 
@@ -695,21 +699,16 @@
             "An instance of SeekableTransitionState has been used in different Transitions. " +
                 "Previous instance: ${this.transition}, new instance: $transition"
         }
-        if (this.transition == null) {
-            observer.start()
-        }
         this.transition = transition
     }
 
     override fun transitionRemoved() {
-        if (this.transition != null) {
-            observer.stop()
-            this.transition = null
-        }
+        this.transition = null
+        SeekableStateObserver.clear(this)
     }
 
     internal fun observeTotalDuration() {
-        observer.observeReads(
+        SeekableStateObserver.observeReads(
             scope = this,
             >
             block = recalculateTotalDurationNanos
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
index a268210..16782be 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
@@ -42,13 +42,13 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.Text
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.Alignment
@@ -71,7 +71,6 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -2475,7 +2474,6 @@
         repeat(6) {
             rule.waitForIdle()
             rule.mainClock.advanceTimeByFrame()
-            println("LTD, scaleX: ${scaleX[selected]}, detailX: $detailScaleX")
             assertEquals(scaleX[selected], scaleY[selected])
             assertEquals(detailScaleX, detailScaleY)
 
@@ -2635,7 +2633,6 @@
                                                 .background(colors[item % 4]),
                                         )
                                         Spacer(Modifier.size(15.dp))
-                                        Text("Item $item")
                                     }
                                 }
                             }
@@ -2653,10 +2650,6 @@
                                         .background(colors[item % 4])
                                         .fillMaxWidth(),
                                 )
-                                Text(
-                                    "Item $item",
-                                    fontSize = 23.sp
-                                )
                             }
                         }
                     }
@@ -2666,28 +2659,18 @@
         rule.waitForIdle()
         rule.mainClock.autoAdvance = false
         state = Screen.Details(5)
-        repeat(10) {
+        repeat(3) {
             rule.waitForIdle()
             rule.mainClock.advanceTimeByFrame()
         }
         state = Screen.List
-        repeat(3) {
-            rule.waitForIdle()
-            rule.mainClock.advanceTimeByFrame()
-        }
+        rule.waitForIdle()
+        rule.mainClock.advanceTimeByFrame()
 
         repeat(3) {
             repeat(5) {
                 rule.runOnIdle {
                     runBlocking {
-                        lazyListState.scrollToItem(it * 10)
-                    }
-                }
-            }
-            rule.mainClock.advanceTimeByFrame()
-            repeat(5) {
-                rule.runOnIdle {
-                    runBlocking {
                         lazyListState.scrollToItem(40 - it * 10)
                     }
                 }
@@ -2707,6 +2690,99 @@
                 }
             }
         }
+        rule.mainClock.autoAdvance = false
+    }
+
+    @Test
+    fun vigorouslyScrollingSharedElementsInLazyList() {
+        var state by mutableStateOf<Screen>(Screen.List)
+        val lazyListState = LazyListState()
+
+        @Suppress("PrimitiveInCollection")
+        val colors = listOf(
+            Color(0xffff6f69),
+            Color(0xffffcc5c),
+            Color(0xff2a9d84),
+            Color(0xff264653)
+        )
+        rule.setContent {
+            SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
+                LazyColumn(state = lazyListState) {
+                    items(50) { item ->
+                        AnimatedVisibility(visible = (state as? Screen.Details)?.item != item) {
+                            Row(modifier = Modifier.fillMaxWidth()) {
+                                Box(
+                                    modifier = Modifier
+                                        .size(100.dp)
+                                        .then(
+                                            Modifier.sharedElement(
+                                                rememberSharedContentState(
+                                                    key = "item-image$item"
+                                                ),
+                                                this@AnimatedVisibility,
+                                            )
+                                        )
+                                        .background(colors[item % 4]),
+                                )
+                                Spacer(Modifier.size(15.dp))
+                            }
+                        }
+                    }
+                }
+
+                AnimatedVisibility(visible = state is Screen.Details) {
+                    var item: Int? by remember { mutableStateOf(null) }
+                    if (state is Screen.Details) {
+                        item = (state as Screen.Details).item
+                    }
+                    Column(
+                        modifier = Modifier
+                            .fillMaxSize()
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .sharedElement(
+                                    rememberSharedContentState(key = "item-image$item"),
+                                    this@AnimatedVisibility,
+                                )
+                                .fillMaxWidth()
+                                .background(colors[item!! % 4])
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        state = Screen.Details(5)
+        repeat(5) {
+            rule.waitForIdle()
+            rule.mainClock.advanceTimeByFrame()
+        }
+        state = Screen.List
+        repeat(3) {
+            rule.waitForIdle()
+            rule.mainClock.advanceTimeByFrame()
+        }
+
+        repeat(5) {
+            rule.runOnIdle {
+                runBlocking {
+                    lazyListState.scrollToItem(it + 1)
+                }
+            }
+            rule.mainClock.advanceTimeByFrame()
+        }
+        repeat(20) {
+            rule.runOnIdle {
+                val id = Random.nextInt(0, 20)
+                runBlocking {
+                    lazyListState.scrollToItem(id)
+                }
+            }
+            rule.mainClock.advanceTimeByFrame()
+        }
     }
 }
 
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
index 62b5f02..9b964d2 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
@@ -263,9 +263,9 @@
 
         layer.record {
             this@draw.drawContent()
-            if (VisualDebugging) {
+            if (VisualDebugging && sharedElement.foundMatch) {
                 // TODO: also draw border of the clip path
-                drawRect(Color.Red, style = Stroke(3f))
+                drawRect(Color.Green, style = Stroke(3f))
             }
         }
         if (state.shouldRenderInPlace) {
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
index 10d63ec..2a9aafd 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
@@ -218,7 +218,7 @@
 
     val nonNullLookaheadSize: Size
         get() = requireNotNull(lookaheadCoords()) {
-            "Error: lookahead coordinates is null."
+            "Error: lookahead coordinates is null for ${sharedElement.key}."
         }.size.toSize()
     var lookaheadCoords: () -> LayoutCoordinates? = { null }
     override var parentState: SharedElementInternalState? = null
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedTransitionScope.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedTransitionScope.kt
index 799827c..74cef36 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedTransitionScope.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedTransitionScope.kt
@@ -860,44 +860,67 @@
         clipInOverlayDuringTransition: OverlayClip,
     ) = composed {
         val key = sharedContentState.key
-        val sharedElement = remember(key) { sharedElementsFor(key) }
+        val sharedElementState = key(key) {
+            val sharedElement = remember { sharedElementsFor(key) }
 
-        @Suppress("UNCHECKED_CAST")
-        val boundsTransition = key(key, parentTransition) {
-            if (parentTransition != null) {
-                parentTransition.createChildTransition(key.toString()) { visible(it) }
-            } else {
-                val targetState =
-                    (visible as (Unit) -> Boolean).invoke(Unit)
-                val transitionState = remember {
-                    MutableTransitionState(
-                        initialState = if (sharedElement.currentBounds != null) {
-                            // In the transition that we completely own, we could make the
-                            // assumption that if a new shared element is added, it'll
-                            // always animate from current bounds to target bounds. This ensures
-                            // continuity of shared element bounds.
-                            !targetState
-                        } else {
-                            targetState
-                        }
+            val boundsAnimation = key(parentTransition) {
+                val boundsTransition = if (parentTransition != null) {
+                    parentTransition.createChildTransition(key.toString()) { visible(it) }
+                } else {
+                    @Suppress("UNCHECKED_CAST")
+                    val targetState =
+                        (visible as (Unit) -> Boolean).invoke(Unit)
+                    val transitionState = remember {
+                        MutableTransitionState(
+                            initialState = if (sharedElement.currentBounds != null) {
+                                // In the transition that we completely own, we could make the
+                                // assumption that if a new shared element is added, it'll
+                                // always animate from current bounds to target bounds. This ensures
+                                // continuity of shared element bounds.
+                                !targetState
+                            } else {
+                                targetState
+                            }
+                        )
+                    }.also { it.targetState = targetState }
+                    rememberTransition(transitionState)
+                }
+                val animation = key(isTransitionActive) {
+                    boundsTransition.createDeferredAnimation(Rect.VectorConverter)
+                }
+                remember(boundsTransition) {
+                    BoundsAnimation(
+                        this@SharedTransitionScope, boundsTransition, animation, boundsTransform
                     )
-                }.also { it.targetState = targetState }
-                rememberTransition(transitionState)
+                }.also { it.updateAnimation(animation, boundsTransform) }
             }
-        }
-        val animation = key(isTransitionActive) {
-            boundsTransition.createDeferredAnimation(Rect.VectorConverter)
-        }
-
-        val boundsAnimation = remember(boundsTransition) {
-            BoundsAnimation(
-                this@SharedTransitionScope, boundsTransition, animation, boundsTransform
+            rememberSharedElementState(
+                sharedElement = sharedElement,
+                boundsAnimation = boundsAnimation,
+                placeHolderSize = placeHolderSize,
+                renderOnlyWhenVisible = renderOnlyWhenVisible,
+                sharedContentState = sharedContentState,
+                clipInOverlayDuringTransition = clipInOverlayDuringTransition,
+                zIndexInOverlay = zIndexInOverlay,
+                renderInOverlayDuringTransition = renderInOverlayDuringTransition
             )
-        }.also {
-            it.updateAnimation(animation, boundsTransform)
         }
 
-        val sharedElementState = remember(key) {
+        this then SharedBoundsNodeElement(sharedElementState)
+    }
+
+    @Composable
+    private fun rememberSharedElementState(
+        sharedElement: SharedElement,
+        boundsAnimation: BoundsAnimation,
+        placeHolderSize: PlaceHolderSize,
+        renderOnlyWhenVisible: Boolean,
+        sharedContentState: SharedContentState,
+        clipInOverlayDuringTransition: OverlayClip,
+        zIndexInOverlay: Float,
+        renderInOverlayDuringTransition: Boolean
+    ): SharedElementInternalState =
+        remember {
             SharedElementInternalState(
                 sharedElement,
                 boundsAnimation,
@@ -921,9 +944,6 @@
             it.userState = sharedContentState
         }
 
-        this then SharedBoundsNodeElement(sharedElementState)
-    }
-
     internal lateinit var root: LayoutCoordinates
     internal lateinit var lookaheadRoot: LayoutCoordinates
 
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index e026507..f8dc027 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -1132,6 +1132,7 @@
 
   public static sealed interface LazyLayoutPrefetchState.PrefetchHandle {
     method public void cancel();
+    method public void markAsUrgent();
   }
 
   public final class Lazy_androidKt {
@@ -1156,8 +1157,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchRequestScope {
-    method public long getAvailableTimeNanos();
-    property public abstract long availableTimeNanos;
+    method public long availableTimeNanos();
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchScheduler {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index cc020da..8ffe7e9 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -1134,6 +1134,7 @@
 
   public static sealed interface LazyLayoutPrefetchState.PrefetchHandle {
     method public void cancel();
+    method public void markAsUrgent();
   }
 
   public final class Lazy_androidKt {
@@ -1158,8 +1159,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchRequestScope {
-    method public long getAvailableTimeNanos();
-    property public abstract long availableTimeNanos;
+    method public long availableTimeNanos();
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchScheduler {
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt
new file mode 100644
index 0000000..bb2d93c
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextBenchmark.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.benchmark.text
+
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkFirstDraw
+import androidx.compose.testutils.benchmark.benchmarkFirstLayout
+import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.compose.testutils.benchmark.benchmarkLayoutPerf
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.text.benchmark.TextBenchmarkTestRule
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.SmallTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BasicTextField2ToggleTextBenchmark(
+    private val textLength: Int
+) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "length={0}")
+        fun initParameters(): Array<Any> = arrayOf(32, 512).filterForCi()
+    }
+
+    private val textBenchmarkRule = TextBenchmarkTestRule()
+    private val benchmarkRule = ComposeBenchmarkRule()
+
+    @get:Rule
+    val testRule = RuleChain
+        .outerRule(textBenchmarkRule)
+        .around(benchmarkRule)
+
+    private val width = textBenchmarkRule.widthDp.dp
+    private val fontSize = textBenchmarkRule.fontSizeSp.sp
+
+    private val caseFactory = {
+        textBenchmarkRule.generator { generator ->
+            BasicTextField2ToggleTextTestCase(
+                textGenerator = generator,
+                textLength = textLength,
+                textNumber = textBenchmarkRule.repeatTimes,
+                width = width,
+                fontSize = fontSize
+            )
+        }
+    }
+
+    /**
+     * Measure the time taken to compose a [BasicTextField] composable from scratch with the
+     * given input. This is the time taken to call the [BasicTextField] composable function.
+     */
+    @Test
+    fun first_compose() {
+        benchmarkRule.benchmarkFirstCompose(caseFactory)
+    }
+
+    /**
+     * Measure the time taken by the first time measure the [BasicTextField] composable with the
+     * given input. This is mainly the time used to measure all the [Measurable]s in the
+     * [BasicTextField] composable.
+     */
+    @Test
+    fun first_measure() {
+        benchmarkRule.benchmarkFirstMeasure(caseFactory)
+    }
+
+    /**
+     * Measure the time taken by the first time layout the [BasicTextField] composable with the
+     * given input.
+     */
+    @Test
+    fun first_layout() {
+        benchmarkRule.benchmarkFirstLayout(caseFactory)
+    }
+
+    /**
+     * Measure the time taken by first time draw the [BasicTextField] composable with the given
+     * input.
+     */
+    @Test
+    fun first_draw() {
+        benchmarkRule.benchmarkFirstDraw(caseFactory)
+    }
+
+    /**
+     * Measure the time taken by layout the [BasicTextField] composable after the layout
+     * constrains changed. This is mainly the time used to re-measure and re-layout the composable.
+     */
+    @Test
+    fun layout() {
+        benchmarkRule.benchmarkLayoutPerf(caseFactory)
+    }
+
+    /**
+     * Measure the time taken to recompose the [BasicTextField] composable when text gets toggled.
+     */
+    @Test
+    fun toggleText_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose(caseFactory, requireRecomposition = false)
+    }
+
+    /**
+     * Measure the time taken to measure the [BasicTextField] composable when text gets toggled.
+     */
+    @Test
+    fun toggleText_measure() {
+        benchmarkRule.toggleStateBenchmarkMeasure(
+            caseFactory = caseFactory,
+            toggleCausesRecompose = false,
+            assertOneRecomposition = false
+        )
+    }
+
+    /**
+     * Measure the time taken to layout the [BasicTextField] composable when text gets toggled.
+     */
+    @Test
+    fun toggleText_layout() {
+        benchmarkRule.toggleStateBenchmarkLayout(
+            caseFactory = caseFactory,
+            assertOneRecomposition = false,
+            toggleCausesRecompose = false
+        )
+    }
+
+    /**
+     * Measure the time taken to draw the [BasicTextField] composable when text gets toggled.
+     */
+    @Test
+    fun toggleText_draw() {
+        benchmarkRule.toggleStateBenchmarkDraw(
+            caseFactory = caseFactory,
+            toggleCausesRecompose = false,
+            assertOneRecomposition = false
+        )
+    }
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt
new file mode 100644
index 0000000..c22686e
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/BasicTextField2ToggleTextTestCase.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package androidx.compose.foundation.benchmark.text
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.benchmark.RandomTextGenerator
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import kotlinx.coroutines.awaitCancellation
+
+@OptIn(ExperimentalComposeUiApi::class)
+class BasicTextField2ToggleTextTestCase(
+    private val textGenerator: RandomTextGenerator,
+    private val textLength: Int,
+    private val textNumber: Int,
+    private val width: Dp,
+    private val fontSize: TextUnit
+) : LayeredComposeTestCase(), ToggleableTestCase {
+
+    private val states = List(textNumber) {
+        TextFieldState(textGenerator.nextParagraph(length = textLength))
+    }
+
+    @Composable
+    override fun MeasuredContent() {
+        for (state in states) {
+            BasicTextField(
+                state = state,
+                textStyle = TextStyle(color = Color.Black, fontSize = fontSize),
+                modifier = Modifier
+                    .background(color = Color.Cyan)
+                    .requiredWidth(width)
+            )
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        Column(
+            modifier = Modifier
+                .width(width)
+                .verticalScroll(rememberScrollState())
+        ) {
+            InterceptPlatformTextInput(
+                interceptor = { _, _ -> awaitCancellation() },
+                content = content
+            )
+        }
+    }
+
+    override fun toggleState() {
+        states.forEach {
+            it.setTextAndPlaceCursorAtEnd(textGenerator.nextParagraph(length = textLength))
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AccessibilityNodeInspector.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AccessibilityNodeInspector.kt
new file mode 100644
index 0000000..f1050e7
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AccessibilityNodeInspector.kt
@@ -0,0 +1,1232 @@
+/*
+ * 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.compose.foundation.demos
+
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.annotation.RequiresApi
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.Button
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.darkColors
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.toComposeIntRect
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireLayoutCoordinates
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.unit.round
+import androidx.compose.ui.unit.toOffset
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.compose.ui.window.Popup
+import androidx.compose.ui.window.PopupPositionProvider
+import androidx.compose.ui.window.PopupProperties
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+private const val InspectorButtonTestTag =
+    "androidx.compose.foundation.demos.AccessibilityNodeInspectorButton"
+
+/** The key used to read Compose testTag semantics properties from accessibility nodes' extras. */
+private const val TestTagExtrasKey = "androidx.compose.ui.semantics.testTag"
+
+private const val LogTag = "A11yNodeInspector"
+
+private val UnsupportedMessage =
+    "This tool is not supported on this device. AccessibilityNodeInfo objects are not readable " +
+        "by code in the same process without an accessibility service before API 34.\n\n" +
+        "This device is running API ${Build.VERSION.SDK_INT}."
+
+private const val UsageMessage =
+    "Drag anywhere to explore accessibility nodes.\n\n" +
+        "Release to view the node's properties and print the information to logcat " +
+        "(tagged \"$LogTag\").\n\n" +
+        "Go back to close inspector."
+
+/**
+ * A composable that, when touched or dragged, will immediately show an overlay on the current
+ * window that allows the user to interactively explore accessibility nodes and view their
+ * properties.
+ */
+@Composable
+fun AccessibilityNodeInspectorButton(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit
+) {
+    var active by remember { mutableStateOf(false) }
+    val state = rememberAccessibilityNodeInspectorState()
+    Box(
+        propagateMinConstraints = true,
+        modifier = modifier
+            // This node needs to have the same gesture modifier as the dedicated inspector overlay
+            // since when the button is dragged initially, the pointer events will all still be sent
+            // to the button, and not the overlay, even though the overlay will immediately be
+            // shown. Because node coordinates are all communicated in screen space, it doesn't
+            // actually matter which window accepts the pointer events.
+            .then(NodeSelectionGestureModifier(state,  active = true }))
+            // Tag the button so the inspector can detect when the button itself is selected and
+            // show a help message.
+            .semantics(mergeDescendants = true) {
+                testTag = InspectorButtonTestTag
+            }
+    ) {
+        content()
+
+        if (active) {
+            if (Build.VERSION.SDK_INT >= 34) {
+                AccessibilityNodeInspector(
+                    state = state,
+                     active = false }
+                )
+            } else {
+                AlertDialog(
+                     active = false },
+                    title = { Text("Accessibility Node Inspector") },
+                    text = { Text(UnsupportedMessage) },
+                    buttons = {
+                        Button(
+                             active = false },
+                            modifier = Modifier
+                                .padding(16.dp)
+                                .fillMaxWidth()
+                        ) {
+                            Text("DISMISS")
+                        }
+                    }
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Returns true if this [NodeInfo] or any of its ancestors represents an
+ * [AccessibilityNodeInspectorButton].
+ */
+private val NodeInfo.isInspectorButton: Boolean
+    get() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            visitSelfAndParents {
+                val testTag = AccessibilityNodeInfoHelper.readExtraData(
+                    it.nodeInfo.unwrap(),
+                    TestTagExtrasKey
+                )
+                if (testTag == InspectorButtonTestTag) {
+                    return true
+                }
+            }
+        }
+        return false
+    }
+
+// region Selection UI
+
+/**
+ * A popup that overlays another window and allows exploring its accessibility nodes by touch.
+ */
+@Composable
+private fun AccessibilityNodeInspector(
+    state: AccessibilityNodeInspectorState,
+    onDismissRequest: () -> Unit,
+) {
+    if (state.isReady) {
+        Popup(
+            popupPositionProvider = state,
+            properties = PopupProperties(
+                focusable = true,
+                excludeFromSystemGesture = false,
+            ),
+            >
+        ) {
+            Box(
+                propagateMinConstraints = true,
+                modifier = Modifier
+                    .width { state.inspectorWindowSize.width }
+                    .height { state.inspectorWindowSize.height }
+            ) {
+                // Selection UI and input handling.
+                Box(
+                    Modifier
+                        .then(NodeSelectionGestureModifier(state))
+                        .then(DrawSelectionOverlayModifier(state))
+                )
+
+                state.selectedNode?.let {
+                    if (it.isInspectorButton) {
+                        // Don't use Surface here, it breaks touch input.
+                        Text(
+                            UsageMessage,
+                            modifier = Modifier
+                                .wrapContentSize()
+                                .padding(16.dp)
+                                .background(MaterialTheme.colors.surface)
+                                .padding(16.dp)
+                        )
+                    } else {
+                        InspectorNodeDetailsDialog(
+                            leafNode = it,
+                            >
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * A modifier that draws the current selection of an [AccessibilityNodeInspectorState] in an
+ * [AccessibilityNodeInspector].
+ */
+private data class DrawSelectionOverlayModifier(
+    val state: AccessibilityNodeInspectorState
+) : ModifierNodeElement<DrawSelectionOverlayModifierNode>() {
+    override fun create(): DrawSelectionOverlayModifierNode =
+        DrawSelectionOverlayModifierNode(state)
+
+    override fun update(node: DrawSelectionOverlayModifierNode) {
+        check(node.state === state) { "Cannot change state" }
+    }
+
+    override fun InspectorInfo.inspectableProperties() {}
+}
+
+private class DrawSelectionOverlayModifierNode(
+    val state: AccessibilityNodeInspectorState
+) : Modifier.Node(), DrawModifierNode {
+    override fun ContentDrawScope.draw() {
+        val coords = requireLayoutCoordinates()
+        state.nodes.let { nodes ->
+            if (nodes.isNotEmpty()) {
+                val layerAlpha = 0.8f / nodes.size
+                nodes.fastForEach { node ->
+                    val bounds = coords.screenToLocal(node.boundsInScreen)
+                    clipRect(
+                        left = bounds.left.toFloat(),
+                        top = bounds.top.toFloat(),
+                        right = bounds.right.toFloat(),
+                        bottom = bounds.bottom.toFloat(),
+                        clipOp = ClipOp.Difference
+                    ) {
+                        drawRect(Color.Black.copy(alpha = layerAlpha))
+                    }
+                }
+                val lastBounds = coords.screenToLocal(nodes.last().boundsInScreen)
+                drawRect(
+                    Color.Green,
+                    style = Stroke(1.dp.toPx()),
+                    topLeft = lastBounds.topLeft.toOffset(),
+                    size = lastBounds.size.toSize()
+                )
+            }
+        }
+
+        state.selectionOffset.takeIf { it.isSpecified }?.let { screenOffset ->
+            val localOffset = coords.screenToLocal(screenOffset)
+            drawLine(
+                Color.Red,
+                start = Offset(0f, localOffset.y),
+                end = Offset(size.width, localOffset.y)
+            )
+            drawLine(
+                Color.Red,
+                start = Offset(localOffset.x, 0f),
+                end = Offset(localOffset.x, size.height)
+            )
+        }
+    }
+
+    private fun LayoutCoordinates.screenToLocal(rect: IntRect): IntRect {
+        return IntRect(
+            topLeft = screenToLocal(rect.topLeft.toOffset()).round(),
+            bottomRight = screenToLocal(rect.bottomRight.toOffset()).round(),
+        )
+    }
+}
+
+/**
+ * A modifier that accepts pointer input to select accessibility nodes in an
+ * [AccessibilityNodeInspectorState].
+ */
+private data class NodeSelectionGestureModifier(
+    val state: AccessibilityNodeInspectorState,
+    val onDragStarted: (() -> Unit)? = null,
+) : ModifierNodeElement<NodeSelectionGestureModifierNode>() {
+    override fun create(): NodeSelectionGestureModifierNode =
+        NodeSelectionGestureModifierNode(state, onDragStarted)
+
+    override fun update(node: NodeSelectionGestureModifierNode) {
+        check(node.state === state) { "Cannot change state" }
+        node.>
+    }
+
+    override fun InspectorInfo.inspectableProperties() {}
+}
+
+private class NodeSelectionGestureModifierNode(
+    val state: AccessibilityNodeInspectorState,
+    var onDragStarted: (() -> Unit)?,
+) : DelegatingNode() {
+
+    private val pass = PointerEventPass.Initial
+
+    @Suppress("unused")
+    private val inputNode = delegate(SuspendingPointerInputModifierNode {
+        // Detect drag gestures but without slop.
+        val layoutCoords = requireLayoutCoordinates()
+        awaitEachGesture {
+            try {
+                val firstChange = awaitFirstDown(pass = pass)
+                state.updateNodeSelection(firstChange.position, layoutCoords)
+                onDragStarted?.invoke()
+                firstChange.consume()
+
+                while (true) {
+                    val event = awaitPointerEvent(pass = pass)
+                    event.changes.fastFirstOrNull { it.id == firstChange.id }?.let { change ->
+                        if (change.changedToUp()) {
+                            return@awaitEachGesture
+                        } else {
+                            state.updateNodeSelection(change.position, layoutCoords)
+                        }
+                    }
+                }
+            } finally {
+                state.showNodeDescription()
+            }
+        }
+    })
+}
+
+// endregion
+
+// region Details UI
+
+/**
+ * A dialog that shows all the properties of [leafNode] and all its ancestors and allows exploring
+ * them interactively.
+ */
+@Composable
+private fun InspectorNodeDetailsDialog(
+    leafNode: NodeInfo,
+    onBack: () -> Unit,
+) {
+    Dialog(
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+        >
+    ) {
+        MaterialTheme(colors = if (isSystemInDarkTheme()) darkColors() else lightColors()) {
+            Surface(
+                modifier = Modifier.padding(16.dp),
+                elevation = 4.dp
+            ) {
+                Column {
+                    TopAppBar(
+                        title = { Text("AccessibilityNodeInfos") },
+                        navigationIcon = {
+                            IconButton( {
+                                Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
+                            }
+                        }
+                    )
+
+                    val nodesFromRoot =
+                        buildList { leafNode.visitSelfAndParents(::add) }.asReversed()
+                    var selectedNodeIndex by remember {
+                        mutableIntStateOf(nodesFromRoot.size - 1)
+                    }
+                    Accordion(
+                        selectedIndex = selectedNodeIndex,
+                         selectedNodeIndex = it },
+                        modifier = Modifier
+                            .verticalScroll(rememberScrollState())
+                            .padding(16.dp)
+                    ) {
+                        nodesFromRoot.forEach {
+                            item({ NodeAccordionHeader(it) }) {
+                                NodeAccordionBody(it)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun NodeAccordionHeader(node: NodeInfo) {
+    val (nodeClassPackage, nodeClassName) = node.nodeInfo.parseClassPackageAndName()
+    Text(nodeClassName, fontWeight = FontWeight.Medium)
+    Spacer(Modifier.width(8.dp))
+    Text(
+        nodeClassPackage,
+        style = MaterialTheme.typography.caption,
+        modifier = Modifier.alpha(0.5f),
+        overflow = TextOverflow.Ellipsis,
+        softWrap = false,
+    )
+}
+
+@Composable
+private fun NodeAccordionBody(node: NodeInfo) {
+    SelectionContainer {
+        Column(modifier = Modifier.padding(bottom = 8.dp)) {
+            val properties = node.getProperties()
+            KeyValueView(elements = properties.toList())
+        }
+    }
+}
+
+/**
+ * Shows a table of keys and their values. Values are rendered using [PropertyValueRepresentation].
+ */
+@Composable
+private fun KeyValueView(elements: List<Pair<String, Any?>>) {
+    Column(verticalArrangement = spacedBy(8.dp)) {
+        elements.forEach { (name, value) ->
+            KeyValueRow(name, value)
+        }
+    }
+}
+
+/**
+ * A row inside a [KeyValueView] that shows a single key and its value. The value will be shown
+ * beside the row, if there's space, otherwise it will be placed below it.
+ */
+@Composable
+private fun KeyValueRow(name: String, value: Any?) {
+    KeyValueRowLayout(
+        contentPadding = 8.dp,
+        keyContent = {
+            Text(
+                name,
+                fontWeight = FontWeight.Medium,
+                style = MaterialTheme.typography.caption,
+                modifier = Modifier.alpha(0.5f)
+            )
+        },
+        valueContent = {
+            val valueRepresentation = PropertyValueRepresentation(value)
+            if (valueRepresentation.customRenderer != null) {
+                valueRepresentation.customRenderer.invoke()
+            } else {
+                Text(
+                    valueRepresentation.text,
+                    fontFamily = FontFamily.Monospace,
+                    modifier = Modifier.horizontalScroll(rememberScrollState())
+                )
+            }
+        }
+    )
+}
+
+/**
+ * Places [keyContent] and [valueContent] on the same line if they both fit with [contentPadding]
+ * spacing, otherwise places [valueContent] below [keyContent] and indents it by [contentPadding].
+ * If [valueContent] wraps and fills all available space, a thin line is drawn in the margin to help
+ * visually track the nesting level.
+ */
+@Composable
+private inline fun KeyValueRowLayout(
+    contentPadding: Dp,
+    keyContent: @Composable RowScope.() -> Unit,
+    valueContent: @Composable RowScope.() -> Unit,
+) {
+    var nestingIndicator: Pair<Offset, Offset>? by remember { mutableStateOf(null) }
+
+    Layout(
+        modifier = Modifier.drawBehind {
+            nestingIndicator?.let { (start, end) ->
+                drawLine(
+                    start = start,
+                    end = end,
+                    color = Color.Gray,
+                    alpha = 0.3f,
+                    strokeWidth = 1.dp.toPx(),
+                )
+            }
+        },
+        content = {
+            Row(content = keyContent)
+            Row(content = valueContent)
+        },
+        measurePolicy = { measurables, constraints ->
+            val contentPaddingPx = contentPadding.roundToPx()
+            val (keyMeasurable, valueMeasurable) = measurables
+            val keyConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+            // contentPadding will either act as the spacing between items if they fit on the same
+            // line, or indent if content wraps, so inset the constraints either way.
+            val valueConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+                .offset(horizontal = -contentPaddingPx)
+            val keyPlaceable = keyMeasurable.measure(keyConstraints)
+            val valuePlaceable = valueMeasurable.measure(valueConstraints)
+            val wrap =
+                keyPlaceable.width + contentPaddingPx + valuePlaceable.width > constraints.maxWidth
+
+            val totalWidth = constraints.maxWidth
+            val totalHeight = if (wrap) {
+                keyPlaceable.height + valuePlaceable.height
+            } else {
+                maxOf(keyPlaceable.height, valuePlaceable.height)
+            }
+
+            // Only draw the nesting indicator if the value filled its max width, which indicates it
+            // will probably be taller, and harder to track the start edge visually.
+            nestingIndicator = if (wrap && valuePlaceable.width == valueConstraints.maxWidth) {
+                Pair(
+                    Offset(contentPaddingPx / 2f, keyPlaceable.height.toFloat()),
+                    Offset(contentPaddingPx / 2f, totalHeight.toFloat())
+                )
+            } else {
+                null
+            }
+
+            layout(totalWidth, totalHeight) {
+                val valueX = totalWidth - valuePlaceable.width
+                if (wrap) {
+                    // Arrange vertically.
+                    keyPlaceable.placeRelative(0, 0)
+                    valuePlaceable.placeRelative(valueX, keyPlaceable.height)
+                } else {
+                    // Arrange horizontally.
+                    val keyY = Alignment.CenterVertically.align(
+                        size = keyPlaceable.height,
+                        space = totalHeight
+                    )
+                    keyPlaceable.placeRelative(0, keyY)
+
+                    val valueY = Alignment.CenterVertically.align(
+                        size = valuePlaceable.height,
+                        space = totalHeight
+                    )
+                    valuePlaceable.placeRelative(valueX, valueY)
+                }
+            }
+        }
+    )
+}
+
+/**
+ * A representation of an arbitrary value as a potentially-styled [AnnotatedString], and optionally
+ * also as a completely custom composable. To create an instance for standard types, call the
+ * [PropertyValueRepresentation] function.
+ */
+private data class PropertyValueRepresentation(
+    val text: AnnotatedString,
+    val customRenderer: (@Composable () -> Unit)? = null
+)
+
+private val ValueTypeTextStyle = TextStyle(
+    fontFamily = FontFamily.Monospace,
+)
+
+/**
+ * Creates a [PropertyValueRepresentation] appropriate for certain well-known types. For other types
+ * returns a representation that is just the result of the value's [toString].
+ */
+private fun PropertyValueRepresentation(value: Any?): PropertyValueRepresentation =
+    when (value) {
+        is CharSequence -> PropertyValueRepresentation(value.toFormattedDebugString())
+
+        is Iterable<*> -> {
+            val valueType = value.javaClass.canonicalName ?: value.javaClass.name
+            // No isEmpty on iterable.
+            if (!value.iterator().hasNext()) {
+                PropertyValueRepresentation(AnnotatedString("$valueType()"))
+            } else {
+                PropertyValueRepresentation(AnnotatedString(value.toString())) {
+                    Column {
+                        Text(valueType, style = ValueTypeTextStyle)
+                        KeyValueView(value.mapIndexed { index, element ->
+                            Pair("[$index]", element)
+                        })
+                    }
+                }
+            }
+        }
+
+        is Map<*, *> -> {
+            val valueType = value.javaClass.canonicalName ?: value.javaClass.name
+            if (value.isEmpty()) {
+                PropertyValueRepresentation(AnnotatedString("$valueType()"))
+            } else {
+                PropertyValueRepresentation(AnnotatedString(value.toString())) {
+                    Column {
+                        Text(valueType, style = ValueTypeTextStyle)
+                        KeyValueView(value.entries.map { (key, value) ->
+                            Pair(key.toString(), value)
+                        })
+                    }
+                }
+            }
+        }
+
+        is Bundle -> {
+            if (value.isEmpty) {
+                PropertyValueRepresentation(
+                    AnnotatedString(
+                        "empty Bundle",
+                        SpanStyle(fontStyle = FontStyle.Italic)
+                    )
+                )
+            } else {
+                PropertyValueRepresentation(AnnotatedString(value.toString())) {
+                    KeyValueView(value.keySet().map { key ->
+                        @Suppress("DEPRECATION")
+                        Pair(key, value.get(key))
+                    })
+                }
+            }
+        }
+
+        else -> PropertyValueRepresentation(AnnotatedString(value.toString()))
+    }
+
+/**
+ * Returns the package and simple name parts of a FQCN by splitting at the last '.' character.
+ */
+private fun AccessibilityNodeInfoCompat.parseClassPackageAndName(): Pair<String, String> {
+    val separatorIndex = className.indexOfLast { it == '.' }
+    return Pair(
+        className.substring(0, separatorIndex),
+        className.substring(separatorIndex + 1)
+    )
+}
+
+/**
+ * A column of expandable headers. Only one header can be expanded at a time. To create an item
+ * call [AccordionScope.item] in [content].
+ */
+@Composable
+private fun Accordion(
+    selectedIndex: Int,
+    onSelectIndex: (Int) -> Unit,
+    modifier: Modifier = Modifier,
+    content: AccordionScope.() -> Unit
+) {
+    Column(modifier) {
+        // Don't rebuild the items every time the selection changes.
+        val items by remember(content) { derivedStateOf { buildAccordionItems(content) } }
+        val isSelectedIndexValid = selectedIndex in items.indices
+        items.fastForEachIndexed { index, item ->
+            val isItemSelected = index == selectedIndex
+            AccordionItemView(
+                item = item,
+                headerHeight = 40.dp,
+                isExpanded = isItemSelected,
+                shrinkHeader = !isItemSelected && isSelectedIndexValid,
+                >
+                    onSelectIndex(if (selectedIndex == index) -1 else index)
+                },
+            )
+            if (index < items.size - 1) {
+                Divider()
+            }
+        }
+    }
+}
+
+/**
+ * An item header and optionally-visible content inside an [Accordion]. Only intended to be called
+ * by [Accordion] itself.
+ */
+@Composable
+private fun AccordionItemView(
+    item: AccordionItem,
+    headerHeight: Dp,
+    isExpanded: Boolean,
+    shrinkHeader: Boolean,
+    onHeaderClicked: () -> Unit
+) {
+    // Shrink collapsed headers to give more space to the expanded body.
+    val headerScale by animateFloatAsState(if (shrinkHeader) 0.8f else 1f, label = "headerScale")
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier
+            .height { (headerHeight * headerScale).roundToPx() }
+            .fillMaxWidth()
+            .selectable(selected = isExpanded, >
+            .graphicsLayer {
+                scaleX = headerScale
+                scaleY = headerScale
+                transformOrigin = TransformOrigin(0f, 0.5f)
+            },
+    ) {
+        val iconRotation by animateFloatAsState(
+            if (isExpanded) 0f else -90f,
+            label = "iconRotation"
+        )
+        Icon(
+            Icons.Filled.ArrowDropDown,
+            contentDescription = null,
+            modifier = Modifier.graphicsLayer {
+                rotationZ = iconRotation
+            })
+        item.header()
+    }
+    AnimatedVisibility(
+        visible = isExpanded,
+        enter = expandVertically(expandFrom = Alignment.Top),
+        exit = shrinkVertically(shrinkTowards = Alignment.Top),
+    ) {
+        item.content()
+    }
+}
+
+private interface AccordionScope {
+    /**
+     * Creates an accordion item with a [header] that is always visible, and a [body] that is only
+     * visible when the item is expanded.
+     */
+    fun item(
+        header: @Composable () -> Unit,
+        body: @Composable () -> Unit
+    )
+}
+
+private data class AccordionItem(
+    val header: @Composable () -> Unit,
+    val content: @Composable () -> Unit
+)
+
+private fun buildAccordionItems(content: AccordionScope.() -> Unit): List<AccordionItem> {
+    return buildList {
+        content(object : AccordionScope {
+            override fun item(
+                header: @Composable () -> Unit,
+                body: @Composable () -> Unit
+            ) {
+                add(AccordionItem(header, body))
+            }
+        })
+    }
+}
+
+/**
+ * Sets [key] to [value] in this map if [value] is not [unspecifiedValue] (null by default).
+ */
+private fun MutableMap<String, Any?>.setIfSpecified(
+    key: String,
+    value: Any?,
+    unspecifiedValue: Any? = null
+) {
+    if (value != unspecifiedValue) {
+        set(key, value)
+    }
+}
+
+/**
+ * Sets [key] to [value] in this map if [value] is not [unspecifiedValue] (false by default).
+ */
+private fun MutableMap<String, Any?>.setIfSpecified(
+    key: String,
+    value: Boolean,
+    unspecifiedValue: Boolean = false
+) {
+    if (value != unspecifiedValue) {
+        set(key, value)
+    }
+}
+
+/**
+ * Sets [key] to [value] in this map if [value] is not [unspecifiedValue] (0 by default).
+ */
+private fun MutableMap<String, Any?>.setIfSpecified(
+    key: String,
+    value: Int,
+    unspecifiedValue: Int = 0
+) {
+    if (value != unspecifiedValue) {
+        set(key, value)
+    }
+}
+
+/**
+ * Returns an [AnnotatedString] that makes this [CharSequence] value easier to read for debugging.
+ * Wraps the value in stylized quote marks so empty strings are more clear, and replaces invisible
+ * control characters (e.g. `'\n'`) with their stylized literal escape sequences.
+ */
+private fun CharSequence.toFormattedDebugString(): AnnotatedString = buildAnnotatedString {
+    val quoteStyle = SpanStyle(
+        color = Color.Gray,
+        fontWeight = FontWeight.Bold
+    )
+    val specialStyle = SpanStyle(
+        color = Color.Red,
+        fontWeight = FontWeight.Bold,
+    )
+
+    withStyle(quoteStyle) { append('"') }
+
+    this@toFormattedDebugString.forEach { c ->
+        var formattedChar: String? = null
+        when (c) {
+            '\n' -> formattedChar = "\\n"
+            '\r' -> formattedChar = "\\r"
+            '\t' -> formattedChar = "\\t"
+            '\b' -> formattedChar = "\\b"
+        }
+        if (formattedChar != null) {
+            withStyle(specialStyle) {
+                append(formattedChar)
+            }
+        } else {
+            append(c)
+        }
+    }
+
+    withStyle(quoteStyle) { append('"') }
+}
+
+// endregion
+
+/**
+ * Like the standard [Modifier.width] modifier but the width is only calculated at measure time.
+ */
+private fun Modifier.width(calculateWidth: Density.() -> Int): Modifier =
+    layout { measurable, constraints ->
+        val calculatedWidth = calculateWidth()
+        val childConstraints = constraints.copy(
+            minWidth = calculatedWidth,
+            maxWidth = calculatedWidth
+        )
+        val placeable = measurable.measure(childConstraints)
+        layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
+        }
+    }
+
+/**
+ * Like the standard [Modifier.height] modifier but the height is only calculated at measure time.
+ */
+private fun Modifier.height(calculateHeight: Density.() -> Int): Modifier =
+    layout { measurable, constraints ->
+        val calculatedHeight = calculateHeight()
+        val childConstraints = constraints.copy(
+            minHeight = calculatedHeight,
+            maxHeight = calculatedHeight
+        )
+        val placeable = measurable.measure(childConstraints)
+        layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
+        }
+    }
+
+// region Accessibility node access
+
+/**
+ * Creates and remembers an [AccessibilityNodeInspectorState] for inspecting the nodes in the window
+ * hosting this composition.
+ */
+@Composable
+private fun rememberAccessibilityNodeInspectorState(): AccessibilityNodeInspectorState {
+    val hostView = LocalView.current
+    val state = remember(hostView) { AccessibilityNodeInspectorState(hostView = hostView) }
+    LaunchedEffect(state) { state.runWhileDisplayed() }
+    return state
+}
+
+/** State holder for an [AccessibilityNodeInspectorButton]. */
+private class AccessibilityNodeInspectorState(
+    private val hostView: View
+) : PopupPositionProvider,
+    View.OnLayoutChangeListener {
+
+    var inspectorWindowSize: IntSize by mutableStateOf(calculateInspectorWindowSize())
+        private set
+
+    private val service: InspectableTreeProvider =
+        if (Build.VERSION.SDK_INT >= 34) {
+            AccessibilityTreeInspectorApi34(hostView.rootView)
+        } else {
+            NoopTreeProvider
+        }
+
+    val isReady: Boolean by derivedStateOf {
+        inspectorWindowSize.width > 0 && inspectorWindowSize.height > 0
+    }
+
+    var selectionOffset: Offset by mutableStateOf(Offset.Unspecified)
+        private set
+
+    var nodes: List<NodeInfo> by mutableStateOf(emptyList())
+        private set
+
+    var selectedNode: NodeInfo? by mutableStateOf(null)
+        private set
+
+    /**
+     * Temporarily select the node at [localOffset] in the window being inspected. This should be
+     * called while the user is dragging.
+     */
+    fun updateNodeSelection(localOffset: Offset, layoutCoordinates: LayoutCoordinates) {
+        hideNodeDescription()
+        val screenOffset = layoutCoordinates.localToScreen(localOffset)
+        selectionOffset = screenOffset
+        this.nodes = service.findNodesAt(screenOffset)
+    }
+
+    /**
+     * Locks in the currently-selected node and shows the inspector dialog with the nodes'
+     * properties.
+     */
+    fun showNodeDescription() {
+        selectionOffset = Offset.Unspecified
+        selectedNode = nodes.lastOrNull()?.also {
+            it.dumpToLog(tag = LogTag)
+        }
+    }
+
+    /**
+     * Hides the inspector dialog to allow the user to select a different node.
+     */
+    fun hideNodeDescription() {
+        selectedNode = null
+    }
+
+    /**
+     * Runs any coroutine effects the state holder requires while it's connected to some UI.
+     */
+    suspend fun runWhileDisplayed() {
+        coroutineScope {
+            // Update the overlay window size when the target window is resized.
+            launch {
+                hostView.addOnLayoutChangeListener(this@AccessibilityNodeInspectorState)
+                try {
+                    awaitCancellation()
+                } finally {
+                    hostView.removeOnLayoutChangeListener(this@AccessibilityNodeInspectorState)
+                }
+            }
+        }
+    }
+
+    override fun onLayoutChange(
+        v: View?,
+        left: Int,
+        top: Int,
+        right: Int,
+        bottom: Int,
+        oldLeft: Int,
+        oldTop: Int,
+        oldRight: Int,
+        oldBottom: Int
+    ) {
+        inspectorWindowSize = calculateInspectorWindowSize()
+    }
+
+    override fun calculatePosition(
+        anchorBounds: IntRect,
+        windowSize: IntSize,
+        layoutDirection: LayoutDirection,
+        popupContentSize: IntSize
+    ): IntOffset = IntOffset.Zero
+
+    private fun calculateInspectorWindowSize(): IntSize {
+        return Rect().also {
+            hostView.getWindowVisibleDisplayFrame(it)
+        }.let { IntSize(it.width(), it.height()) }
+    }
+}
+
+private data class NodeInfo(
+    val nodeInfo: AccessibilityNodeInfoCompat,
+    val boundsInScreen: IntRect,
+)
+
+/** Returns a map with all the inspectable properties of this [NodeInfo]. */
+private fun NodeInfo.getProperties(): Map<String, Any?> = buildMap {
+    val node = nodeInfo
+    // Don't render className, it's in the title.
+    setIfSpecified("packageName", node.packageName)
+    setIfSpecified("boundsInScreen", Rect().also(node::getBoundsInScreen))
+    setIfSpecified("boundsInWindow", Rect().also(node::getBoundsInWindow))
+    setIfSpecified("viewIdResourceName", node.viewIdResourceName)
+    setIfSpecified("uniqueId", node.uniqueId)
+    setIfSpecified("text", node.text)
+    setIfSpecified("textSelectionStart", node.textSelectionStart, unspecifiedValue = -1)
+    setIfSpecified("textSelectionEnd", node.textSelectionEnd, unspecifiedValue = -1)
+    setIfSpecified("contentDescription", node.contentDescription)
+    setIfSpecified("collectionInfo", node.collectionInfo)
+    setIfSpecified("collectionItemInfo", node.collectionItemInfo)
+    setIfSpecified("containerTitle", node.containerTitle)
+    setIfSpecified("childCount", node.childCount)
+    setIfSpecified("drawingOrder", node.drawingOrder)
+    setIfSpecified("error", node.error)
+    setIfSpecified("hintText", node.hintText)
+    setIfSpecified("inputType", node.inputType)
+    setIfSpecified("isAccessibilityDataSensitive", node.isAccessibilityDataSensitive)
+    setIfSpecified("isAccessibilityFocused", node.isAccessibilityFocused)
+    setIfSpecified("isCheckable", node.isCheckable)
+    setIfSpecified("isChecked", node.isChecked)
+    setIfSpecified("isClickable", node.isClickable)
+    setIfSpecified("isLongClickable", node.isLongClickable)
+    setIfSpecified("isContextClickable", node.isContextClickable)
+    setIfSpecified("isContentInvalid", node.isContentInvalid)
+    setIfSpecified("isDismissable", node.isDismissable)
+    setIfSpecified("isEditable", node.isEditable)
+    setIfSpecified("isEnabled", node.isEnabled, unspecifiedValue = true)
+    setIfSpecified("isFocusable", node.isFocusable)
+    setIfSpecified("isFocused", node.isFocused)
+    setIfSpecified("isGranularScrollingSupported", node.isGranularScrollingSupported)
+    setIfSpecified("isHeading", node.isHeading)
+    set("isImportantForAccessibility", node.isImportantForAccessibility)
+    setIfSpecified("isMultiLine", node.isMultiLine)
+    setIfSpecified("isPassword", node.isPassword)
+    setIfSpecified("isScreenReaderFocusable", node.isScreenReaderFocusable)
+    setIfSpecified("isScrollable", node.isScrollable)
+    setIfSpecified("isSelected", node.isSelected)
+    setIfSpecified("isShowingHintText", node.isShowingHintText)
+    setIfSpecified("isTextEntryKey", node.isTextEntryKey)
+    setIfSpecified("isTextSelectable", node.isTextSelectable)
+    setIfSpecified("isVisibleToUser", node.isVisibleToUser, unspecifiedValue = true)
+    setIfSpecified("labelFor", node.labelFor)
+    setIfSpecified("labeledBy", node.labeledBy)
+    setIfSpecified("liveRegion", node.liveRegion)
+    setIfSpecified("maxTextLength", node.maxTextLength, unspecifiedValue = -1)
+    setIfSpecified("movementGranularities", node.movementGranularities)
+    setIfSpecified("paneTitle", node.paneTitle)
+    setIfSpecified("rangeInfo", node.rangeInfo)
+    setIfSpecified("roleDescription", node.roleDescription)
+    setIfSpecified("stateDescription", node.stateDescription)
+    setIfSpecified("tooltipText", node.tooltipText)
+    setIfSpecified("touchDelegateInfo", node.touchDelegateInfo)
+    setIfSpecified("windowId", node.windowId, unspecifiedValue = -1)
+    setIfSpecified("canOpenPopup", node.canOpenPopup())
+    setIfSpecified(
+        "hasRequestInitialAccessibilityFocus",
+        node.hasRequestInitialAccessibilityFocus()
+    )
+    setIfSpecified("extras", node.extrasWithoutExtraData)
+    setIfSpecified("extraRenderingInfo", node.extraRenderingInfo)
+
+    if (Build.VERSION.SDK_INT >= 26 && node.availableExtraData.isNotEmpty()) {
+        val extraData = mutableMapOf<String, Any?>()
+        node.availableExtraData.forEach { key ->
+            extraData[key] = AccessibilityNodeInfoHelper.readExtraData(node.unwrap(), key)
+        }
+        setIfSpecified("extraData (from availableExtraData)", extraData)
+    }
+}
+
+/**
+ * Returns the extras bundle, but without any keys from
+ * [AccessibilityNodeInfoCompat.getAvailableExtraData], since those are reported separately.
+ */
+private val AccessibilityNodeInfoCompat.extrasWithoutExtraData: Bundle
+    get() {
+        val extras = Bundle(extras)
+        availableExtraData.forEach {
+            extras.remove(it)
+        }
+        return extras
+    }
+
+/** Class verification helper for reading extras data from an [AccessibilityNodeInfo]. */
+@RequiresApi(26)
+private object AccessibilityNodeInfoHelper {
+    fun readExtraData(
+        node: AccessibilityNodeInfo,
+        key: String
+    ): Any? {
+        if (key in node.availableExtraData && node.refreshWithExtraData(key, Bundle())) {
+            @Suppress("DEPRECATION")
+            return node.extras.get(key)
+        } else {
+            return null
+        }
+    }
+}
+
+private interface InspectableTreeProvider {
+    fun findNodesAt(screenOffset: Offset): List<NodeInfo>
+}
+
+private object NoopTreeProvider : InspectableTreeProvider {
+    override fun findNodesAt(screenOffset: Offset): List<NodeInfo> = emptyList()
+}
+
+@RequiresApi(34)
+private class AccessibilityTreeInspectorApi34(
+    private val rootView: View
+) : InspectableTreeProvider {
+
+    private val matrixCache = Matrix()
+
+    override fun findNodesAt(screenOffset: Offset): List<NodeInfo> {
+        rootView.transformMatrixToLocal(matrixCache)
+
+        val nodes = mutableListOf<NodeInfo>()
+        val rootInfo = rootView.createNodeInfo()
+        rootInfo.visitNodeAndChildren { node ->
+            if (node.hitTest(screenOffset)) {
+                nodes += node
+                true
+            } else {
+                false
+            }
+        }
+        return nodes
+    }
+
+    private fun NodeInfo.hitTest(screenOffset: Offset): Boolean {
+        return boundsInScreen.contains(screenOffset.round())
+    }
+
+    private inline fun NodeInfo.visitNodeAndChildren(
+        visitor: (NodeInfo) -> Boolean
+    ) {
+        val queue = mutableVectorOf(this)
+        while (queue.isNotEmpty()) {
+            val current = queue.removeAt(queue.lastIndex)
+            val visitChildren = visitor(current)
+            if (visitChildren) {
+                for (i in 0 until current.nodeInfo.childCount) {
+                    queue += current.nodeInfo.getChild(i).toNodeInfo()
+                }
+            }
+        }
+    }
+
+    private fun View.createNodeInfo(): NodeInfo {
+        val rawNodeInfo = createAccessibilityNodeInfo()
+        val nodeInfoCompat = AccessibilityNodeInfoCompat.wrap(rawNodeInfo)
+        rawNodeInfo.setQueryFromAppProcessEnabled(this, true)
+        return nodeInfoCompat.toNodeInfo()
+    }
+}
+
+private fun AccessibilityNodeInfoCompat.toNodeInfo(): NodeInfo = NodeInfo(
+    nodeInfo = this,
+    boundsInScreen = Rect().also(::getBoundsInScreen).toComposeIntRect(),
+)
+
+private fun NodeInfo.dumpToLog(tag: String) {
+    val indent = "  "
+    var depth = 0
+    visitSelfAndParents { node ->
+        Log.d(tag, indent.repeat(depth) + node.nodeInfo.unwrap().toString())
+        depth++
+    }
+}
+
+private inline fun NodeInfo.visitSelfAndParents(block: (NodeInfo) -> Unit) {
+    var node: NodeInfo? = this
+    while (node != null) {
+        block(node)
+        node = node.parent
+    }
+}
+
+private val NodeInfo.parent: NodeInfo?
+    get() = nodeInfo.parent?.toNodeInfo()
+
+// endregion
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt
index 087d8db..c4d5b3a 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt
@@ -14,14 +14,20 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.grid
 
 import androidx.compose.foundation.AutoTestFrameClock
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.lazy.layout.TestPrefetchScheduler
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
@@ -58,6 +64,16 @@
     val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
 
     lateinit var state: LazyGridState
+    private val scheduler = TestPrefetchScheduler()
+
+    @OptIn(ExperimentalFoundationApi::class)
+    @Composable
+    fun rememberState(
+        initialFirstVisibleItemIndex: Int = 0,
+        initialFirstVisibleItemScrollOffset: Int = 0
+    ): LazyGridState = remember {
+        LazyGridState(initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset, scheduler)
+    }
 
     @Test
     fun notPrefetchingForwardInitially() {
@@ -85,8 +101,8 @@
             }
         }
 
-        waitForPrefetch(4)
-        waitForPrefetch(5)
+        waitForPrefetch()
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertExists()
@@ -106,8 +122,8 @@
             }
         }
 
-        waitForPrefetch(2)
-        waitForPrefetch(3)
+        waitForPrefetch()
+        waitForPrefetch()
 
         rule.onNodeWithTag("2")
             .assertExists()
@@ -127,8 +143,8 @@
             }
         }
 
-        waitForPrefetch(6)
-        waitForPrefetch(7)
+        waitForPrefetch()
+        waitForPrefetch()
 
         rule.onNodeWithTag("6")
             .assertExists()
@@ -144,8 +160,8 @@
             }
         }
 
-        waitForPrefetch(0)
-        waitForPrefetch(1)
+        waitForPrefetch()
+        waitForPrefetch()
 
         rule.onNodeWithTag("0")
             .assertExists()
@@ -165,7 +181,7 @@
             }
         }
 
-        waitForPrefetch(4)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -174,7 +190,7 @@
             }
         }
 
-        waitForPrefetch(6)
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
@@ -194,7 +210,7 @@
             }
         }
 
-        waitForPrefetch(4)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -203,7 +219,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
@@ -225,8 +241,7 @@
             }
         }
 
-        waitForPrefetch(6)
-        waitForPrefetch(7)
+        waitForPrefetch()
 
         rule.onNodeWithTag("6")
             .assertExists()
@@ -244,8 +259,7 @@
             }
         }
 
-        waitForPrefetch(0)
-        waitForPrefetch(1)
+        waitForPrefetch()
 
         rule.onNodeWithTag("0")
             .assertExists()
@@ -283,7 +297,7 @@
             }
         }
 
-        waitForPrefetch(6)
+        waitForPrefetch()
 
         rule.onNodeWithTag("8")
             .assertExists()
@@ -296,7 +310,7 @@
             }
         }
 
-        waitForPrefetch(0)
+        waitForPrefetch()
 
         rule.onNodeWithTag("0")
             .assertExists()
@@ -316,7 +330,7 @@
             ) { constraints ->
                 val placeable = if (emit) {
                     subcompose(Unit) {
-                        state = rememberLazyGridState()
+                        state = rememberState()
                         LazyGrid(
                             2,
                             Modifier.mainAxisSize(itemsSizeDp * 1.5f),
@@ -355,7 +369,7 @@
     fun snappingToOtherPositionWhilePrefetchIsScheduled() {
         val composedItems = mutableListOf<Int>()
         rule.setContent {
-            state = rememberLazyGridState()
+            state = rememberState()
             LazyGrid(
                 1,
                 Modifier.mainAxisSize(itemsSizeDp * 1.5f),
@@ -410,7 +424,7 @@
             }
         }
 
-        waitForPrefetch(13)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking(AutoTestFrameClock()) {
@@ -424,14 +438,13 @@
         }
     }
 
-    private fun waitForPrefetch(index: Int) {
-        rule.waitUntil {
-            activeNodes.contains(index) && activeMeasuredNodes.contains(index)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
     private val activeNodes = mutableSetOf<Int>()
-    private val activeMeasuredNodes = mutableSetOf<Int>()
 
     private fun composeGrid(
         firstItem: Int = 0,
@@ -440,7 +453,7 @@
         contentPadding: PaddingValues = PaddingValues(0.dp)
     ) {
         rule.setContent {
-            state = rememberLazyGridState(
+            state = rememberState(
                 initialFirstVisibleItemIndex = firstItem,
                 initialFirstVisibleItemScrollOffset = itemOffset
             )
@@ -456,7 +469,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -465,7 +477,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index bb6d01a..5596f14 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -257,7 +257,8 @@
                     .then(modifier))
         }
         var needToCompose by mutableStateOf(false)
-        val prefetchState = LazyLayoutPrefetchState()
+        val scheduler = TestPrefetchScheduler()
+        val prefetchState = LazyLayoutPrefetchState(scheduler)
         rule.setContent {
             LazyLayout(itemProvider, prefetchState = prefetchState) {
                 val item = if (needToCompose) {
@@ -273,9 +274,10 @@
             assertThat(measureCount).isEqualTo(0)
 
             prefetchState.schedulePrefetch(0, constraints)
-        }
 
-        rule.waitUntil { measureCount == 1 }
+            scheduler.executeActiveRequests()
+            assertThat(measureCount).isEqualTo(1)
+        }
 
         rule.onNodeWithTag("0").assertIsNotDisplayed()
 
@@ -303,20 +305,18 @@
                 }
             }
         }
-        val prefetchState = LazyLayoutPrefetchState()
+        val scheduler = TestPrefetchScheduler()
+        val prefetchState = LazyLayoutPrefetchState(scheduler)
         rule.setContent {
             LazyLayout(itemProvider, prefetchState = prefetchState) {
                 layout(100, 100) {}
             }
         }
 
-        val handle = rule.runOnIdle {
-            prefetchState.schedulePrefetch(0, Constraints.fixed(50, 50))
-        }
-
-        rule.waitUntil { composed }
-
         rule.runOnIdle {
+            val handle = prefetchState.schedulePrefetch(0, Constraints.fixed(50, 50))
+            scheduler.executeActiveRequests()
+            assertThat(composed).isTrue()
             handle.cancel()
         }
 
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/TestPrefetchScheduler.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/TestPrefetchScheduler.kt
new file mode 100644
index 0000000..ad3b8a6
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/TestPrefetchScheduler.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.compose.foundation.lazy.layout
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class TestPrefetchScheduler : PrefetchScheduler {
+
+    private var activeRequests = mutableListOf<PrefetchRequest>()
+    override fun schedulePrefetch(prefetchRequest: PrefetchRequest) {
+        activeRequests.add(prefetchRequest)
+    }
+
+    fun executeActiveRequests() {
+        activeRequests.forEach {
+            with(it) { scope.execute() }
+        }
+        activeRequests.clear()
+    }
+
+    private val scope = object : PrefetchRequestScope {
+        override fun availableTimeNanos(): Long = Long.MAX_VALUE
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
index b8a37d5..21f1531 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.runtime.SideEffect
 import androidx.compose.ui.Modifier
@@ -45,6 +48,10 @@
 
     private val composedItems = mutableSetOf<Int>()
 
+    private val state = LazyListState().also {
+        it.prefetchingEnabled = false
+    }
+
     @Test
     fun moveFocus() {
         // Arrange.
@@ -52,7 +59,7 @@
         lateinit var focusManager: FocusManager
         rule.setContent {
             focusManager = LocalFocusManager.current
-            LazyRow(Modifier.size(rowSize)) {
+            LazyRow(Modifier.size(rowSize), state) {
                 items(100) { index ->
                     Box(
                         Modifier
@@ -71,7 +78,7 @@
         rule.runOnIdle { focusManager.moveFocus(FocusDirection.Right) }
 
         // Assert
-        rule.runOnIdle { assertThat(composedItems).containsExactly(5, 6) }
+        rule.runOnIdle { assertThat(composedItems).containsExactly(5) }
     }
 
     @Test
@@ -81,7 +88,7 @@
         lateinit var focusManager: FocusManager
         rule.setContent {
             focusManager = LocalFocusManager.current
-            LazyRow(Modifier.size(rowSize)) {
+            LazyRow(Modifier.size(rowSize), state) {
                 items(100) { index ->
                     Box(Modifier.size(itemSize).focusable()) {
                         Box(Modifier.size(itemSize).focusable().testTag("$index"))
@@ -97,7 +104,7 @@
         rule.runOnIdle { focusManager.moveFocus(FocusDirection.Right) }
 
         // Assert
-        rule.runOnIdle { assertThat(composedItems).containsExactly(5, 6) }
+        rule.runOnIdle { assertThat(composedItems).containsExactly(5) }
     }
 
     @Test
@@ -107,7 +114,7 @@
         lateinit var focusManager: FocusManager
         rule.setContent {
             focusManager = LocalFocusManager.current
-            LazyRow(Modifier.size(rowSize)) {
+            LazyRow(Modifier.size(rowSize), state) {
                 items(100) { index ->
                     Box(Modifier.size(itemSize).focusable()) {
                         Box(Modifier.size(itemSize).focusable()) {
@@ -125,6 +132,6 @@
         rule.runOnIdle { focusManager.moveFocus(FocusDirection.Right) }
 
         // Assert
-        rule.runOnIdle { assertThat(composedItems).containsExactly(5, 6) }
+        rule.runOnIdle { assertThat(composedItems).containsExactly(5) }
     }
 }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListNestedPrefetchingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListNestedPrefetchingTest.kt
index ebd489b..229f84a 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListNestedPrefetchingTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListNestedPrefetchingTest.kt
@@ -26,6 +26,8 @@
 import androidx.compose.foundation.lazy.LazyListPrefetchStrategy
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.layout.NestedPrefetchScope
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
+import androidx.compose.foundation.lazy.layout.TestPrefetchScheduler
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.remember
@@ -70,11 +72,19 @@
     private val itemsSizePx = 30
     private val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
     private val activeNodes = mutableSetOf<String>()
-    private val activeMeasuredNodes = mutableSetOf<String>()
+    private val scheduler = TestPrefetchScheduler()
+
+    @OptIn(ExperimentalFoundationApi::class)
+    private val strategy = object : LazyListPrefetchStrategy by LazyListPrefetchStrategy() {
+        override val prefetchScheduler: PrefetchScheduler = scheduler
+    }
+
+    @OptIn(ExperimentalFoundationApi::class)
+    private fun createState(): LazyListState = LazyListState(prefetchStrategy = strategy)
 
     @Test
     fun nestedPrefetchingForwardAfterSmallScroll() {
-        val state = LazyListState()
+        val state = createState()
         composeList(state)
 
         val prefetchIndex = 2
@@ -85,7 +95,7 @@
                 }
             }
 
-            waitForPrefetch(tagFor(prefetchIndex))
+            waitForPrefetch()
         }
 
         // We want to make sure nested children were precomposed before the parent was premeasured
@@ -111,7 +121,7 @@
 
     @Test
     fun cancelingPrefetchCancelsItsNestedPrefetches() {
-        val state = LazyListState()
+        val state = createState()
         composeList(state)
 
         rule.runOnIdle {
@@ -122,7 +132,7 @@
             }
         }
 
-        waitForPrefetch(tagFor(3))
+        waitForPrefetch()
 
         rule.runOnIdle {
             assertThat(activeNodes).contains(tagFor(3))
@@ -141,7 +151,7 @@
             }
         }
 
-        waitForPrefetch(tagFor(7))
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking(AutoTestFrameClock()) {
@@ -160,7 +170,7 @@
     @OptIn(ExperimentalFoundationApi::class)
     @Test
     fun overridingNestedPrefetchCountIsRespected() {
-        val state = LazyListState()
+        val state = createState()
         composeList(
             state,
             createNestedLazyListState = {
@@ -177,7 +187,7 @@
                 }
             }
 
-            waitForPrefetch(tagFor(prefetchIndex))
+            waitForPrefetch()
         }
 
         // Since the nested prefetch count on the strategy is 1, we only expect index 0 to be
@@ -197,7 +207,7 @@
     fun nestedPrefetchIsMeasuredWithProvidedConstraints() {
         val nestedConstraints =
             Constraints(minWidth = 20, minHeight = 20, maxWidth = 20, maxHeight = 20)
-        val state = LazyListState()
+        val state = createState()
         composeList(
             state,
             createNestedLazyListState = {
@@ -214,7 +224,7 @@
                 }
             }
 
-            waitForPrefetch(tagFor(prefetchIndex))
+            waitForPrefetch()
         }
 
         assertThat(actions).containsExactly(
@@ -232,7 +242,7 @@
 
     @Test
     fun nestedPrefetchStartsFromFirstVisibleItemIndex() {
-        val state = LazyListState()
+        val state = createState()
         composeList(
             state,
             createNestedLazyListState = {
@@ -247,7 +257,7 @@
                 }
             }
 
-            waitForPrefetch(tagFor(prefetchIndex))
+            waitForPrefetch()
         }
 
         assertThat(actions).containsExactly(
@@ -273,9 +283,9 @@
         }
     }
 
-    private fun waitForPrefetch(tag: String) {
-        rule.waitUntil {
-            activeNodes.contains(tag) && activeMeasuredNodes.contains(tag)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
@@ -332,17 +342,14 @@
             actions?.add(Action.Compose(index, nestedIndex))
             onDispose {
                 activeNodes.remove(tag)
-                activeMeasuredNodes.remove(tag)
             }
         }
     }
 
     private fun Modifier.trackWhenMeasured(index: Int, nestedIndex: Int? = null): Modifier {
-        val tag = tagFor(index, nestedIndex)
         return this then Modifier.layout { measurable, constraints ->
             actions?.add(Action.Measure(index, nestedIndex))
             val placeable = measurable.measure(constraints)
-            activeMeasuredNodes.add(tag)
             layout(placeable.width, placeable.height) {
                 placeable.place(0, 0)
             }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt
index 31dd332..0d773c2 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt
@@ -29,9 +29,10 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.NestedPrefetchScope
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
+import androidx.compose.foundation.lazy.layout.TestPrefetchScheduler
 import androidx.compose.foundation.lazy.list.LazyListPrefetchStrategyTest.RecordingLazyListPrefetchStrategy.Callback
 import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
@@ -74,10 +75,11 @@
     private val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
 
     lateinit var state: LazyListState
+    private val scheduler = TestPrefetchScheduler()
 
     @Test
     fun callbacksTriggered_whenScrollForwardsWithoutVisibleItemsChanged() {
-        val strategy = RecordingLazyListPrefetchStrategy()
+        val strategy = RecordingLazyListPrefetchStrategy(scheduler)
 
         composeList(prefetchStrategy = strategy)
 
@@ -104,7 +106,7 @@
 
     @Test
     fun callbacksTriggered_whenScrollBackwardsWithoutVisibleItemsChanged() {
-        val strategy = RecordingLazyListPrefetchStrategy()
+        val strategy = RecordingLazyListPrefetchStrategy(scheduler)
 
         composeList(firstItem = 10, itemOffset = 10, prefetchStrategy = strategy)
 
@@ -131,7 +133,7 @@
 
     @Test
     fun callbacksTriggered_whenScrollWithVisibleItemsChanged() {
-        val strategy = RecordingLazyListPrefetchStrategy()
+        val strategy = RecordingLazyListPrefetchStrategy(scheduler)
 
         composeList(prefetchStrategy = strategy)
 
@@ -161,7 +163,7 @@
 
     @Test
     fun callbacksTriggered_whenItemsChangedWithoutScroll() {
-        val strategy = RecordingLazyListPrefetchStrategy()
+        val strategy = RecordingLazyListPrefetchStrategy(scheduler)
         val numItems = mutableStateOf(100)
 
         composeList(prefetchStrategy = strategy, numItems = numItems)
@@ -196,20 +198,17 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
         rule.onNodeWithTag("2")
             .assertExists()
     }
 
-    private fun waitForPrefetch(index: Int) {
-        rule.waitUntil {
-            activeNodes.contains(index) && activeMeasuredNodes.contains(index)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
-    private val activeNodes = mutableSetOf<Int>()
-    private val activeMeasuredNodes = mutableSetOf<Int>()
-
     @OptIn(ExperimentalFoundationApi::class)
     private fun composeList(
         firstItem: Int = 0,
@@ -228,13 +227,6 @@
                 state,
             ) {
                 items(numItems.value) {
-                    DisposableEffect(it) {
-                        activeNodes.add(it)
-                        onDispose {
-                            activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
-                        }
-                    }
                     Spacer(
                         Modifier
                             .mainAxisSize(itemsSizeDp)
@@ -242,7 +234,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
@@ -256,7 +247,10 @@
     /**
      * LazyListPrefetchStrategy that just records callbacks without scheduling prefetches.
      */
-    private class RecordingLazyListPrefetchStrategy : LazyListPrefetchStrategy {
+    private class RecordingLazyListPrefetchStrategy(
+        override val prefetchScheduler: PrefetchScheduler?
+    ) : LazyListPrefetchStrategy {
+
         sealed interface Callback {
             data class OnScroll(val delta: Float, val visibleIndices: List<Int>) : Callback
             data class OnVisibleItemsUpdated(val visibleIndices: List<Int>) : Callback
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt
index 70a88d8..ef1aed5 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt
@@ -22,7 +22,10 @@
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.lazy.LazyListPrefetchStrategy
 import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
+import androidx.compose.foundation.lazy.layout.TestPrefetchScheduler
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.Modifier
@@ -71,6 +74,13 @@
 
     lateinit var state: LazyListState
 
+    private val scheduler = TestPrefetchScheduler()
+
+    @OptIn(ExperimentalFoundationApi::class)
+    private val strategy = object : LazyListPrefetchStrategy by LazyListPrefetchStrategy() {
+        override val prefetchScheduler: PrefetchScheduler = scheduler
+    }
+
     @Test
     fun notPrefetchingForwardInitially() {
         composeList()
@@ -97,7 +107,7 @@
             }
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertExists()
@@ -115,7 +125,7 @@
             }
         }
 
-        waitForPrefetch(1)
+        waitForPrefetch()
 
         rule.onNodeWithTag("1")
             .assertExists()
@@ -134,7 +144,7 @@
             }
         }
         var prefetchIndex = initialIndex + 2
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -149,7 +159,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -167,7 +177,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -178,7 +188,7 @@
 
         val prefetchIndex = 3
 
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("${prefetchIndex - 1}")
             .assertIsDisplayed()
@@ -198,7 +208,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -207,7 +217,7 @@
             }
         }
 
-        waitForPrefetch(1)
+        waitForPrefetch()
 
         rule.onNodeWithTag("2")
             .assertIsDisplayed()
@@ -230,7 +240,7 @@
 
         var prefetchIndex = initialIndex + 2
 
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -245,7 +255,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -281,7 +291,7 @@
         }
 
         var prefetchIndex = initialIndex + 1
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("${prefetchIndex + 1}")
             .assertExists()
@@ -295,7 +305,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -458,7 +468,7 @@
             }
         }
 
-        waitForPrefetch(7)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking(AutoTestFrameClock()) {
@@ -472,14 +482,13 @@
         }
     }
 
-    private fun waitForPrefetch(index: Int) {
-        rule.waitUntil {
-            activeNodes.contains(index) && activeMeasuredNodes.contains(index)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
     private val activeNodes = mutableSetOf<Int>()
-    private val activeMeasuredNodes = mutableSetOf<Int>()
 
     private fun composeList(
         firstItem: Int = 0,
@@ -488,9 +497,11 @@
         contentPadding: PaddingValues = PaddingValues(0.dp)
     ) {
         rule.setContent {
+            @OptIn(ExperimentalFoundationApi::class)
             state = rememberLazyListState(
                 initialFirstVisibleItemIndex = firstItem,
-                initialFirstVisibleItemScrollOffset = itemOffset
+                initialFirstVisibleItemScrollOffset = itemOffset,
+                prefetchStrategy = strategy
             )
             LazyColumnOrRow(
                 Modifier.mainAxisSize(itemsSizeDp * 1.5f),
@@ -504,7 +515,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -514,7 +524,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
index 224a1cc..72084b7 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.staggeredgrid
 
 import androidx.compose.foundation.AutoTestFrameClock
@@ -22,7 +24,10 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.lazy.layout.TestPrefetchScheduler
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.Remeasurement
@@ -63,6 +68,20 @@
     val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
 
     internal lateinit var state: LazyStaggeredGridState
+    private val scheduler = TestPrefetchScheduler()
+
+    @OptIn(ExperimentalFoundationApi::class)
+    @Composable
+    fun rememberState(
+        initialFirstVisibleItemIndex: Int = 0,
+        initialFirstVisibleItemOffset: Int = 0
+    ): LazyStaggeredGridState = remember {
+        LazyStaggeredGridState(
+            intArrayOf(initialFirstVisibleItemIndex),
+            intArrayOf(initialFirstVisibleItemOffset),
+            scheduler
+        )
+    }
 
     @Test
     fun notPrefetchingForwardInitially() {
@@ -90,7 +109,7 @@
             }
         }
 
-        waitForPrefetch(5)
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertExists()
@@ -110,7 +129,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.onNodeWithTag("2")
             .assertExists()
@@ -130,7 +149,7 @@
             }
         }
 
-        waitForPrefetch(9)
+        waitForPrefetch()
 
         rule.onNodeWithTag("8")
             .assertExists()
@@ -145,7 +164,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.onNodeWithTag("2")
             .assertExists()
@@ -165,7 +184,7 @@
             }
         }
 
-        waitForPrefetch(4)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -174,7 +193,7 @@
             }
         }
 
-        waitForPrefetch(6)
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
@@ -194,7 +213,7 @@
             }
         }
 
-        waitForPrefetch(4)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -203,7 +222,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
@@ -226,7 +245,7 @@
             }
         }
 
-        waitForPrefetch(13)
+        waitForPrefetch()
 
         rule.onNodeWithTag("12")
             .assertExists()
@@ -378,7 +397,7 @@
     fun snappingToOtherPositionWhilePrefetchIsScheduled() {
         val composedItems = mutableListOf<Int>()
         rule.setContent {
-            state = rememberLazyStaggeredGridState()
+            state = rememberState()
             LazyStaggeredGrid(
                 1,
                 Modifier.mainAxisSize(itemsSizeDp * 1.5f),
@@ -433,7 +452,7 @@
             }
         }
 
-        waitForPrefetch(13)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking(AutoTestFrameClock()) {
@@ -450,7 +469,7 @@
     @Test
     fun scrollingWithStaggeredItemsPrefetchesCorrectly() {
         rule.setContent {
-            state = rememberLazyStaggeredGridState()
+            state = rememberState()
             LazyStaggeredGrid(
                 2,
                 Modifier.mainAxisSize(itemsSizeDp * 5f),
@@ -461,7 +480,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -471,7 +489,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
@@ -495,8 +512,8 @@
             }
         }
 
-        waitForPrefetch(7)
-        waitForPrefetch(8)
+        waitForPrefetch()
+        waitForPrefetch()
 
         // ┌─┬─┐
         // │2├─┤
@@ -520,14 +537,14 @@
         // │6├─┤
         // └─┴─┘
 
-        waitForPrefetch(9)
+        waitForPrefetch()
     }
 
     @Test
     fun fullSpanIsPrefetchedCorrectly() {
         val nodeConstraints = mutableMapOf<Int, Constraints>()
         rule.setContent {
-            state = rememberLazyStaggeredGridState()
+            state = rememberState()
             LazyStaggeredGrid(
                 2,
                 Modifier.mainAxisSize(itemsSizeDp * 5f).crossAxisSize(itemsSizeDp * 2f),
@@ -546,7 +563,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -555,7 +571,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 nodeConstraints.put(it, constraints)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
@@ -577,7 +592,7 @@
         state.scrollBy(itemsSizeDp * 5f)
         assertThat(activeNodes).contains(9)
 
-        waitForPrefetch(10)
+        waitForPrefetch()
         val expectedConstraints = if (vertical) {
             Constraints.fixedWidth(itemsSizePx * 2)
         } else {
@@ -589,7 +604,7 @@
     @Test
     fun fullSpanIsPrefetchedCorrectly_scrollingBack() {
         rule.setContent {
-            state = rememberLazyStaggeredGridState()
+            state = rememberState()
             LazyStaggeredGrid(
                 2,
                 Modifier.mainAxisSize(itemsSizeDp * 5f),
@@ -608,7 +623,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -618,7 +632,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
@@ -647,27 +660,26 @@
 
         state.scrollBy(-1.dp)
 
-        waitForPrefetch(10)
+        waitForPrefetch()
     }
 
-    private fun waitForPrefetch(index: Int) {
-        rule.waitUntil {
-            activeNodes.contains(index) && activeMeasuredNodes.contains(index)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
     private val activeNodes = mutableSetOf<Int>()
-    private val activeMeasuredNodes = mutableSetOf<Int>()
 
     private fun composeStaggeredGrid(
         firstItem: Int = 0,
         itemOffset: Int = 0,
     ) {
-        state = LazyStaggeredGridState(
-            initialFirstVisibleItemIndex = firstItem,
-            initialFirstVisibleItemOffset = itemOffset
-        )
         rule.setContent {
+            state = rememberState(
+                initialFirstVisibleItemIndex = firstItem,
+                initialFirstVisibleItemOffset = itemOffset
+            )
             LazyStaggeredGrid(
                 2,
                 Modifier.mainAxisSize(itemsSizeDp * 1.5f),
@@ -678,7 +690,6 @@
                         activeNodes.add(it)
                         onDispose {
                             activeNodes.remove(it)
-                            activeMeasuredNodes.remove(it)
                         }
                     }
                     Spacer(
@@ -688,7 +699,6 @@
                             .testTag("$it")
                             .layout { measurable, constraints ->
                                 val placeable = measurable.measure(constraints)
-                                activeMeasuredNodes.add(it)
                                 layout(placeable.width, placeable.height) {
                                     placeable.place(0, 0)
                                 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 3b003f7..d57ab48 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -96,7 +96,10 @@
             stickyHeader {
                 Text(
                     "Section $section",
-                    Modifier.fillMaxWidth().background(Color.LightGray).padding(8.dp)
+                    Modifier
+                        .fillMaxWidth()
+                        .background(Color.LightGray)
+                        .padding(8.dp)
                 )
             }
             items(10) {
@@ -109,9 +112,9 @@
 @Sampled
 @Composable
 fun AnimateItemSample() {
-    var list by remember { mutableStateOf(listOf("A", "B", "C")) }
+    var list by remember { mutableStateOf(listOf("1", "2", "3")) }
     Column {
-        Button( list = list + "D" }) {
+        Button( list = list + "${list.count() + 1}" }) {
             Text("Add new item")
         }
         Button( list = list.shuffled() }) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
index c1da808..c49618d5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
@@ -409,6 +409,10 @@
     fun platformMagnifierModifier_updatesProperties_whenZoomChanged() {
         var zoom by mutableStateOf(1f)
         val platformMagnifier = CountingPlatformMagnifier()
+        val factory = PlatformMagnifierFactory(
+            platformMagnifier,
+            canUpdateZoom = true
+        )
         rule.setContent {
             Box(
                 Modifier.magnifier(
@@ -416,10 +420,7 @@
                     magnifierCenter = { Offset.Unspecified },
                     zoom = zoom,
                     >
-                    platformMagnifierFactory = PlatformMagnifierFactory(
-                        platformMagnifier,
-                        canUpdateZoom = true
-                    )
+                    platformMagnifierFactory = factory
                 )
             )
         }
@@ -556,7 +557,7 @@
 
     @SdkSuppress(minSdkVersion = 28)
     @Test
-    fun platformMagnifierModifier_firesOnSizeChanged_initially_whenSourceCenterUnspecified() {
+    fun platformMagnifierModifier_doesNotFireOnSizeChanged_initially_whenSourceCenterUnspecified() {
         val magnifierSize = IntSize(10, 11)
         val sizeEvents = mutableListOf<DpSize>()
         val platformMagnifier = CountingPlatformMagnifier().apply {
@@ -574,6 +575,34 @@
             )
         }
 
+        rule.runOnIdle { assertThat(sizeEvents).isEmpty() }
+    }
+
+    @SdkSuppress(minSdkVersion = 28)
+    @Test
+    fun platformMagnifierModifier_firesOnSizeChanged_afterSourceCenterIsSpecified() {
+        val magnifierSize = IntSize(10, 11)
+        val sizeEvents = mutableListOf<DpSize>()
+        val platformMagnifier = CountingPlatformMagnifier().apply {
+            size = magnifierSize
+        }
+        var sourceCenter by mutableStateOf(Offset.Unspecified)
+        rule.setContent {
+            Box(
+                Modifier.magnifier(
+                    sourceCenter = { sourceCenter },
+                    magnifierCenter = { Offset.Unspecified },
+                    zoom = Float.NaN,
+                     sizeEvents += it },
+                    platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
+                )
+            )
+        }
+
+        rule.runOnIdle { assertThat(sizeEvents).isEmpty() }
+
+        sourceCenter = Offset(1f, 1f)
+
         rule.runOnIdle {
             assertThat(sizeEvents).containsExactly(
                 with(rule.density) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
index bf5658f..dd8c1ce 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
@@ -30,9 +30,11 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -131,13 +133,21 @@
         key: ((index: Int) -> Any)? = null,
         snapPosition: SnapPosition = config.snapPosition.first,
         flingBehavior: TargetedFlingBehavior? = null,
+        prefetchScheduler: PrefetchScheduler? = null,
         pageContent: @Composable PagerScope.(page: Int) -> Unit = { Page(index = it) }
     ) {
 
         rule.setContent {
-            val state = rememberPagerState(initialPage, initialPageOffsetFraction, pageCount).also {
-                pagerState = it
+            val state = if (prefetchScheduler == null) {
+                rememberPagerState(initialPage, initialPageOffsetFraction, pageCount)
+            } else {
+                remember {
+                    object : PagerState(initialPage, initialPageOffsetFraction, prefetchScheduler) {
+                        override val pageCount: Int get() = pageCount()
+                    }
+                }
             }
+            pagerState = state
             composeView = LocalView.current
             focusManager = LocalFocusManager.current
             CompositionLocalProvider(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
index d72d1e3..19d6748 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.pager
 
 import android.view.accessibility.AccessibilityNodeProvider
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -41,6 +42,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
+@OptIn(ExperimentalFoundationApi::class)
 @RunWith(Parameterized::class)
 class PagerAccessibilityTest(config: ParamConfig) : BasePagerTest(config = config) {
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPrefetcherTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPrefetcherTest.kt
index 1f9cc0b..f9414d3 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPrefetcherTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPrefetcherTest.kt
@@ -56,6 +56,7 @@
     var pageSizePx = 300
     val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
     var touchSlope: Float = 0.0f
+    private val scheduler = TestPrefetchScheduler()
 
     @Test
     fun notPrefetchingForwardInitially() {
@@ -83,7 +84,7 @@
             }
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertExists()
@@ -102,7 +103,7 @@
             }
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertExists()
@@ -126,7 +127,7 @@
             up()
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertExists()
@@ -151,7 +152,7 @@
             up()
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertExists()
@@ -170,7 +171,7 @@
             }
         }
         var prefetchIndex = initialIndex + 2
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -185,7 +186,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -203,7 +204,7 @@
             }
         }
 
-        waitForPrefetch(2)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -214,7 +215,7 @@
 
         val prefetchIndex = 3
 
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("${prefetchIndex - 1}")
             .assertIsDisplayed()
@@ -236,7 +237,7 @@
             }
         }
 
-        waitForPrefetch(preFetchIndex)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking {
@@ -245,7 +246,7 @@
             }
         }
 
-        waitForPrefetch(preFetchIndex - 1)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$preFetchIndex")
             .assertIsDisplayed()
@@ -268,7 +269,7 @@
 
         var prefetchIndex = initialIndex + 2
 
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -283,7 +284,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -319,7 +320,7 @@
         }
 
         var prefetchIndex = initialIndex + 1
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("${prefetchIndex + 1}")
             .assertExists()
@@ -333,7 +334,7 @@
         }
 
         prefetchIndex -= 3
-        waitForPrefetch(prefetchIndex)
+        waitForPrefetch()
 
         rule.onNodeWithTag("$prefetchIndex")
             .assertExists()
@@ -457,7 +458,7 @@
             }
         }
 
-        waitForPrefetch(7)
+        waitForPrefetch()
 
         rule.runOnIdle {
             runBlocking(AutoTestFrameClock()) {
@@ -477,14 +478,13 @@
         return consumed
     }
 
-    private fun waitForPrefetch(index: Int) {
-        rule.waitUntil {
-            activeNodes.contains(index) && activeMeasuredNodes.contains(index)
+    private fun waitForPrefetch() {
+        rule.runOnIdle {
+            scheduler.executeActiveRequests()
         }
     }
 
     private val activeNodes = mutableSetOf<Int>()
-    private val activeMeasuredNodes = mutableSetOf<Int>()
 
     private fun composePager(
         initialPage: Int = 0,
@@ -499,6 +499,7 @@
             beyondViewportPageCount = paramConfig.beyondViewportPageCount,
             initialPage = initialPage,
             initialPageOffsetFraction = initialPageOffsetFraction,
+            prefetchScheduler = scheduler,
             pageCount = { 100 },
             pageSize = {
                 object : PageSize {
@@ -516,7 +517,6 @@
                 activeNodes.add(it)
                 onDispose {
                     activeNodes.remove(it)
-                    activeMeasuredNodes.remove(it)
                 }
             }
 
@@ -527,7 +527,6 @@
                     .testTag("$it")
                     .layout { measurable, constraints ->
                         val placeable = measurable.measure(constraints)
-                        activeMeasuredNodes.add(it)
                         layout(placeable.width, placeable.height) {
                             placeable.place(0, 0)
                         }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/TestPrefetchScheduler.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/TestPrefetchScheduler.kt
new file mode 100644
index 0000000..04c60b750
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/TestPrefetchScheduler.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.compose.foundation.pager
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.lazy.layout.PrefetchRequest
+import androidx.compose.foundation.lazy.layout.PrefetchRequestScope
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class TestPrefetchScheduler : PrefetchScheduler {
+
+    private var activeRequests = mutableListOf<PrefetchRequest>()
+    override fun schedulePrefetch(prefetchRequest: PrefetchRequest) {
+        activeRequests.add(prefetchRequest)
+    }
+
+    fun executeActiveRequests() {
+        activeRequests.forEach {
+            with(it) { scope.execute() }
+        }
+        activeRequests.clear()
+    }
+
+    private val scope = object : PrefetchRequestScope {
+        override fun availableTimeNanos(): Long = Long.MAX_VALUE
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
index 5b1a62f..37c4219 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingGestureTest.kt
@@ -23,22 +23,31 @@
 import android.view.inputmethod.InputConnection
 import android.view.inputmethod.InsertGesture
 import android.view.inputmethod.JoinOrSplitGesture
+import android.view.inputmethod.PreviewableHandwritingGesture
 import android.view.inputmethod.RemoveSpaceGesture
 import android.view.inputmethod.SelectGesture
 import android.view.inputmethod.SelectRangeGesture
 import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.setFocusableContent
 import androidx.compose.foundation.text.input.InputMethodInterceptor
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.TextSelectionColors
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixelColor
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.toAndroidRectF
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalTextToolbar
@@ -47,12 +56,15 @@
 import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.TextFieldValue
+import androidx.core.graphics.ColorUtils
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -72,6 +84,10 @@
 
     private val Tag = "CoreTextField"
 
+    private val backgroundColor = Color.Red
+    private val textColor = Color.Green
+    private val selectionColor = Color.Blue
+
     private val lineMargin = 16f
 
     @Test
@@ -96,6 +112,31 @@
     }
 
     @Test
+    fun textField_selectGesture_preview_wordLevel() {
+        val text = "abc def ghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val localBoundingBox = textLayoutResult.boundingBoxOf("b")
+                val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+                SelectGesture.Builder()
+                    .setSelectionArea(screenBoundingBox)
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("abc"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectGesture_characterLevel() {
         val text = "abcdef"
         testHandwritingGesture(
@@ -117,6 +158,31 @@
     }
 
     @Test
+    fun textField_selectGesture_preview_characterLevel() {
+        val text = "abc def ghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val localBoundingBox = textLayoutResult.boundingBoxOf("bc")
+                val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+                SelectGesture.Builder()
+                    .setSelectionArea(screenBoundingBox)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("bc"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectGesture_characterLevel_noSelection_insertFallbackText() {
         val text = "abcdef"
         val fallback = "fallbackText"
@@ -161,6 +227,29 @@
     }
 
     @Test
+    fun textField_selectGesture_preview_characterLevel_noSelection() {
+        val text = "abcdef"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { _ ->
+                SelectGesture.Builder()
+                    .setSelectionArea(RectF(0f, 0f, 1f, 1f))
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertNoHighlight(textLayoutResult)
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectGesture_wordLevel_noSelection_insertFallbackText() {
         val text = "abc def ghi"
         val fallback = "fallback"
@@ -211,6 +300,31 @@
     }
 
     @Test
+    fun textField_selectGesture_preview_wordLevel_noSelection() {
+        val text = "abc def ghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val localBoundingBox = textLayoutResult.boundingBoxOf("a")
+                val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+                SelectGesture.Builder()
+                    .setSelectionArea(screenBoundingBox)
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertNoHighlight(textLayoutResult)
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteGesture_wordLevel_removeSpaceBeforeDeletion() {
         val text = "abc def ghi"
         testHandwritingGesture(
@@ -234,6 +348,31 @@
     }
 
     @Test
+    fun textField_deleteGesture_preview_wordLevel() {
+        val text = "abc def ghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val localBoundingBox = textLayoutResult.boundingBoxOf("h")
+                val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+                DeleteGesture.Builder()
+                    .setDeletionArea(screenBoundingBox)
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("ghi"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteGesture_wordLevel_onlyRemoveSpaceBeforeDeletion() {
         val text = "abc\n def ghi"
         testHandwritingGesture(
@@ -327,6 +466,31 @@
     }
 
     @Test
+    fun textField_deleteGesture_preview_characterLevel() {
+        val text = "abcdefghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val localBoundingBox = textLayoutResult.boundingBoxOf("def")
+                val screenBoundingBox = localToScreen(localBoundingBox).toAndroidRectF()
+                DeleteGesture.Builder()
+                    .setDeletionArea(screenBoundingBox)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("def"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteGesture_characterLevel_notRemoveSpaces() {
         val text = "abcdef ghi"
         testHandwritingGesture(
@@ -397,6 +561,29 @@
         }
     }
 
+    @Test
+    fun textField_deleteGesture_preview_noDeletion() {
+        val text = "abc def ghi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { _ ->
+                DeleteGesture.Builder()
+                    .setDeletionArea(RectF(-1f, -1f, 0f, 0f))
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertNoHighlight(textLayoutResult)
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
     fun textField_selectRangeGesture_characterLevel() {
         val text = "abc\ndef"
         testHandwritingGesture(
@@ -425,6 +612,38 @@
     }
 
     @Test
+    fun textField_selectRangeGesture_preview_characterLevel() {
+        val text = "abc\ndef"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val startArea = textLayoutResult.boundingBoxOf("c").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                val endArea = textLayoutResult.boundingBoxOf("d").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                SelectRangeGesture.Builder()
+                    .setSelectionStartArea(startArea)
+                    .setSelectionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertSelectionPreviewHighlight(textLayoutResult, text.rangeOf("c\nd"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectRangeGesture_wordLevel() {
         val text = "abc\ndef jhi"
         testHandwritingGesture(
@@ -453,6 +672,41 @@
     }
 
     @Test
+    fun textField_selectRangeGesture_preview_wordLevel() {
+        val text = "abc\ndef jhi"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val startArea = textLayoutResult.boundingBoxOf("b").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                val endArea = textLayoutResult.boundingBoxOf("e").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                SelectRangeGesture.Builder()
+                    .setSelectionStartArea(startArea)
+                    .setSelectionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertSelectionPreviewHighlight(
+                    textLayoutResult,
+                    text.rangeOf("abc\ndef")
+                )
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectRangeGesture_nothingSelectedInStartArea_insertFallbackText() {
         val text = "abc\ndef"
         val fallback = "fallbackText"
@@ -515,6 +769,35 @@
     }
 
     @Test
+    fun textField_selectRangeGesture_preview_nothingSelectedInStartArea() {
+        val text = "abc\ndef"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val endArea = textLayoutResult.boundingBoxOf("d").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+                // The startArea selects nothing, but the endArea contains one character, it
+                // should still fallback.
+                SelectRangeGesture.Builder()
+                    .setSelectionStartArea(RectF(0f, 0f, 1f, 1f))
+                    .setSelectionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertNoHighlight(textLayoutResult)
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_selectRangeGesture_noSelection_fail() {
         val text = "abcdef"
         testHandwritingGesture(
@@ -564,6 +847,37 @@
     }
 
     @Test
+    fun textField_deleteRangeGesture_preview_characterLevel() {
+        val text = "abc\ndef"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val startArea = textLayoutResult.boundingBoxOf("c").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+                val endArea = textLayoutResult.boundingBoxOf("d").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                DeleteRangeGesture.Builder()
+                    .setDeletionStartArea(startArea)
+                    .setDeletionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertDeletionPreviewHighlight(textLayoutResult, text.rangeOf("c\nd"))
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteRangeGesture_wordLevel() {
         val text = "abc def\n jhi lmn"
         testHandwritingGesture(
@@ -592,6 +906,39 @@
     }
 
     @Test
+    fun textField_deleteRangeGesture_preview_wordLevel() {
+        val text = "abc def\n jhi lmn"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val startArea = textLayoutResult.boundingBoxOf("e").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+                val endArea = textLayoutResult.boundingBoxOf("h").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+
+                DeleteRangeGesture.Builder()
+                    .setDeletionStartArea(startArea)
+                    .setDeletionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertDeletionPreviewHighlight(
+                    textLayoutResult, text.rangeOf("def\n jhi")
+                )
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteRangeGesture_nothingDeletedInStartArea_insertFallbackText() {
         val text = "abc\ndef"
         val fallback = "fallbackText"
@@ -654,6 +1001,35 @@
     }
 
     @Test
+    fun textField_deleteRangeGesture_preview_nothingDeletedInStartArea() {
+        val text = "abc def\n jhi lmn"
+        val initialCursor = 3
+        testHandwritingGesture(
+            text = text,
+            initialSelection = TextRange(initialCursor),
+            gestureFactory = { textLayoutResult ->
+                val endArea = textLayoutResult.boundingBoxOf("d").let {
+                    localToScreen(it).toAndroidRectF()
+                }
+                // The startArea selects nothing, but the endArea contains one character, it
+                // should still fallback.
+                DeleteRangeGesture.Builder()
+                    .setDeletionStartArea(RectF(0f, 0f, 1f, 1f))
+                    .setDeletionEndArea(endArea)
+                    .setGranularity(HandwritingGesture.GRANULARITY_CHARACTER)
+                    .build()
+            },
+            preview = true,
+            imageAssertion = { imageBitmap, textLayoutResult ->
+                imageBitmap.assertNoHighlight(textLayoutResult)
+            }
+        ) { textFieldValue, _, _ ->
+            assertThat(textFieldValue.text).isEqualTo(text)
+            assertThat(textFieldValue.selection).isEqualTo(TextRange(initialCursor))
+        }
+    }
+
+    @Test
     fun textField_deleteRangeGesture_noDeletion_fail() {
         val text = "abcdef"
         testHandwritingGesture(
@@ -1268,6 +1644,8 @@
         text: String,
         initialSelection: TextRange = TextRange(text.length),
         gestureFactory: LayoutCoordinates.(TextLayoutResult) -> HandwritingGesture,
+        preview: Boolean = false,
+        imageAssertion: ((ImageBitmap, TextLayoutResult) -> Unit)? = null,
         assertion: (TextFieldValue, resultCode: Int, TextToolbar) -> Unit
     ) {
         var textFieldValue by mutableStateOf(TextFieldValue(text, initialSelection))
@@ -1280,14 +1658,20 @@
                 override val handwritingGestureLineMargin: Float = lineMargin
             }
             CompositionLocalProvider(
+                LocalTextSelectionColors provides TextSelectionColors(
+                    selectionColor,
+                    selectionColor
+                ),
                 LocalTextToolbar provides textToolbar,
                 LocalViewConfiguration provides viewConfiguration
             ) {
                 CoreTextField(
                     value = textFieldValue,
                      textFieldValue = it },
+                    textStyle = TextStyle(color = textColor),
                     modifier = Modifier
                         .fillMaxSize()
+                        .background(backgroundColor)
                         .testTag(Tag)
                         .onGloballyPositioned { layoutCoordinates = it },
                     >
@@ -1303,12 +1687,20 @@
         var resultCode = InputConnection.HANDWRITING_GESTURE_RESULT_UNKNOWN
 
         inputMethodInterceptor.withInputConnection {
-            performHandwritingGesture(gesture, /* executor= */null) { resultCode = it }
+            if (preview) {
+                previewHandwritingGesture(gesture as PreviewableHandwritingGesture, null)
+            } else {
+                performHandwritingGesture(gesture, /* executor= */ null) { resultCode = it }
+            }
         }
 
         rule.runOnIdle {
             assertion.invoke(textFieldValue, resultCode, textToolbar)
         }
+
+        if (imageAssertion != null) {
+            imageAssertion(rule.onNodeWithTag(Tag).captureToImage(), textLayoutResult!!)
+        }
     }
 
     private fun setContent(
@@ -1327,6 +1719,56 @@
         return rect.translate(localOriginInScreen)
     }
 
+    private fun ImageBitmap.assertSelectionPreviewHighlight(
+        textLayoutResult: TextLayoutResult,
+        range: TextRange
+    ) {
+        assertHighlight(textLayoutResult, range, selectionColor)
+    }
+
+    private fun ImageBitmap.assertDeletionPreviewHighlight(
+        textLayoutResult: TextLayoutResult,
+        range: TextRange
+    ) {
+        val deletionPreviewColor = textColor.copy(alpha = textColor.alpha * 0.2f)
+        val compositeColor = Color(
+            ColorUtils.compositeColors(
+                deletionPreviewColor.toArgb(),
+                backgroundColor.toArgb()
+            )
+        )
+        assertHighlight(textLayoutResult, range, compositeColor)
+    }
+
+    private fun ImageBitmap.assertNoHighlight(textLayoutResult: TextLayoutResult) {
+        assertHighlight(textLayoutResult, TextRange.Zero, Color.Unspecified)
+    }
+
+    private fun ImageBitmap.assertHighlight(
+        textLayoutResult: TextLayoutResult,
+        range: TextRange,
+        highlightColor: Color
+    ) {
+        val pixelMap =
+            toPixelMap(width = textLayoutResult.size.width, height = textLayoutResult.size.height)
+        for (offset in 0 until textLayoutResult.layoutInput.text.length) {
+            if (textLayoutResult.layoutInput.text[offset] == '\n') {
+                continue
+            }
+            // Check the top left pixel of each character (assumes LTR). This pixel is always part
+            // of the background (not part of the text foreground).
+            val line = textLayoutResult.multiParagraph.getLineForOffset(offset)
+            val lineTop = textLayoutResult.multiParagraph.getLineTop(line).ceilToIntPx()
+            val horizontal =
+                textLayoutResult.multiParagraph.getHorizontalPosition(offset, true).ceilToIntPx()
+            if (offset in range) {
+                pixelMap.assertPixelColor(highlightColor, horizontal, lineTop)
+            } else {
+                pixelMap.assertPixelColor(backgroundColor, horizontal, lineTop)
+            }
+        }
+    }
+
     private fun FakeTextToolbar(): TextToolbar {
         return object : TextToolbar {
             private var _status: TextToolbarStatus = TextToolbarStatus.Hidden
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
index 938bbf7..c5dd825 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
@@ -70,15 +70,52 @@
         TextFieldDelegate.draw(
             canvas = actualCanvas,
             value = TextFieldValue(text = "Hello, World", selection = selection),
-            selectionPaint = Paint().apply { color = selectionColor },
+            selectionPreviewHighlightRange = TextRange.Zero,
+            deletionPreviewHighlightRange = TextRange.Zero,
             offsetMapping = OffsetMapping.Identity,
-            textLayoutResult = layoutResult
+            textLayoutResult = layoutResult,
+            highlightPaint = Paint(),
+            selectionBackgroundColor = selectionColor
         )
 
         assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue()
     }
 
     @Test
+    fun draw_highlight_test() {
+        val textDelegate = TextDelegate(
+            text = AnnotatedString("Hello, World"),
+            style = TextStyle.Default,
+            maxLines = 2,
+            density = density,
+            fontFamilyResolver = fontFamilyResolver
+        )
+        val layoutResult = textDelegate.layout(Constraints.fixedWidth(1024), layoutDirection)
+        val deletionPreviewHighlightRange = TextRange(3, 5)
+
+        val actualBitmap = layoutResult.toBitmap()
+        val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
+        TextFieldDelegate.draw(
+            canvas = actualCanvas,
+            value = TextFieldValue(text = "Hello, World", selection = TextRange.Zero),
+            selectionPreviewHighlightRange = TextRange.Zero,
+            deletionPreviewHighlightRange = deletionPreviewHighlightRange,
+            offsetMapping = OffsetMapping.Identity,
+            textLayoutResult = layoutResult,
+            highlightPaint = Paint(),
+            selectionBackgroundColor = Color.Blue
+        )
+
+        val expectedBitmap = layoutResult.toBitmap()
+        val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
+        val selectionPath = layoutResult.multiParagraph.getPathForRange(3, 5)
+        // Default text color is black, so deletion preview highlight is black with 20% alpha.
+        expectedCanvas.drawPath(selectionPath, Paint().apply { color = Color(0f, 0f, 0f, 0.2f) })
+        TextPainter.paint(expectedCanvas, layoutResult)
+        assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue()
+    }
+
+    @Test
     fun layout_height_constraint_max_height() {
         val textDelegate = TextDelegate(
             text = AnnotatedString("Hello, World"),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
index 683499e..2a4c26e4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
@@ -18,8 +18,13 @@
 
 import android.text.InputType
 import android.view.inputmethod.DeleteGesture
+import android.view.inputmethod.DeleteRangeGesture
 import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InsertGesture
+import android.view.inputmethod.JoinOrSplitGesture
+import android.view.inputmethod.RemoveSpaceGesture
 import android.view.inputmethod.SelectGesture
+import android.view.inputmethod.SelectRangeGesture
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.ImeOptions
@@ -599,8 +604,29 @@
         val info = EditorInfo()
         info.update(ImeOptions.Default)
 
-        assertThat(info.supportedHandwritingGestures).contains(SelectGesture::class.java)
-        assertThat(info.supportedHandwritingGestures).contains(DeleteGesture::class.java)
+        assertThat(info.supportedHandwritingGestures).containsExactly(
+            SelectGesture::class.java,
+            DeleteGesture::class.java,
+            SelectRangeGesture::class.java,
+            DeleteRangeGesture::class.java,
+            JoinOrSplitGesture::class.java,
+            InsertGesture::class.java,
+            RemoveSpaceGesture::class.java,
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun supportedStylusHandwritingGesturePreviews() {
+        val info = EditorInfo()
+        info.update(ImeOptions.Default)
+
+        assertThat(info.supportedHandwritingGesturePreviews).containsExactly(
+            SelectGesture::class.java,
+            DeleteGesture::class.java,
+            SelectRangeGesture::class.java,
+            DeleteRangeGesture::class.java,
+        )
     }
 
     private fun EditorInfo.update(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyTextInputMethodRequestCursorAnchorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyTextInputMethodRequestCursorAnchorInfoTest.kt
index c361a4e..1be5916d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyTextInputMethodRequestCursorAnchorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/LegacyTextInputMethodRequestCursorAnchorInfoTest.kt
@@ -70,7 +70,16 @@
     private val fontFamilyMeasureFont =
         Font(resId = R.font.sample_font, weight = FontWeight.Normal, style = FontStyle.Normal)
             .toFontFamily()
-    private var position = Offset(0f, 0f)
+
+    private var textLayoutOffset = Offset.Zero
+
+    // Inner text field bounds in text layout coordinates
+    private val innerTextFieldBounds =
+        Rect(topLeft = Offset(0f, 40f), bottomRight = Offset(90f, 70f))
+
+    // Decoration box bounds in text layout coordinates
+    private val decorationBoxBounds =
+        Rect(topLeft = Offset(-5f, 35f), bottomRight = Offset(95f, 77f))
 
     private lateinit var textInputService: LegacyTextInputMethodRequest
     private lateinit var inputMethodManager: InputMethodManager
@@ -84,7 +93,11 @@
         inputMethodManager = mock { on { isActive() } doReturn true }
         textInputService = LegacyTextInputMethodRequest(
             view = View(context),
-            localToScreen = { matrix -> matrix.translate(position.x, position.y) },
+            localToScreen = { matrix ->
+                (textLayoutOffset + decorationBoxBounds.topLeft).let {
+                    matrix.translate(it.x, it.y)
+                }
+            },
             inputMethodManager = inputMethodManager
         )
         textInputService.startInput(
@@ -108,11 +121,9 @@
             TextFieldValue("abc", selection = TextRange(2), composition = TextRange(1, 2))
         textInputService.updateState(oldValue = textFieldValue, newValue = textFieldValue)
 
-        position = Offset(1f, 1f)
+        textLayoutOffset = Offset(1f, 1f)
         val offsetMapping = OffsetMapping.Identity
         val textLayoutResult = getTextLayoutResult(textFieldValue.text)
-        val innerTextFieldBounds = Rect.Zero
-        val decorationBoxBounds = Rect.Zero
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -124,7 +135,7 @@
         inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
 
         // Immediate update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected =
             builder.build(
                 textFieldValue,
@@ -137,7 +148,7 @@
         verify(inputMethodManager).updateCursorAnchorInfo(expected)
 
         clearInvocations(inputMethodManager)
-        position = Offset(2f, 2f)
+        textLayoutOffset = Offset(2f, 2f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -161,11 +172,9 @@
         // No immediate update until updateTextLayoutResult call
         verify(inputMethodManager, never()).updateCursorAnchorInfo(any())
 
-        position = Offset(1f, 1f)
+        textLayoutOffset = Offset(1f, 1f)
         val offsetMapping = OffsetMapping.Identity
         val textLayoutResult = getTextLayoutResult(textFieldValue.text)
-        val innerTextFieldBounds = Rect.Zero
-        val decorationBoxBounds = Rect.Zero
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -175,7 +184,7 @@
         )
 
         // Immediate update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected =
             builder.build(
                 textFieldValue,
@@ -188,7 +197,7 @@
         verify(inputMethodManager).updateCursorAnchorInfo(expected)
 
         clearInvocations(inputMethodManager)
-        position = Offset(2f, 2f)
+        textLayoutOffset = Offset(2f, 2f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -207,11 +216,9 @@
             TextFieldValue("abc", selection = TextRange(2), composition = TextRange(1, 2))
         textInputService.updateState(oldValue = textFieldValue, newValue = textFieldValue)
 
-        position = Offset(1f, 1f)
+        textLayoutOffset = Offset(1f, 1f)
         val offsetMapping = OffsetMapping.Identity
         val textLayoutResult = getTextLayoutResult(textFieldValue.text)
-        val innerTextFieldBounds = Rect.Zero
-        val decorationBoxBounds = Rect.Zero
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -226,7 +233,7 @@
         verify(inputMethodManager, never()).updateCursorAnchorInfo(any())
 
         clearInvocations(inputMethodManager)
-        position = Offset(2f, 2f)
+        textLayoutOffset = Offset(2f, 2f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -236,7 +243,7 @@
         )
 
         // Monitoring update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected =
             builder.build(
                 textFieldValue,
@@ -256,7 +263,7 @@
         verify(inputMethodManager, never()).updateCursorAnchorInfo(any())
 
         clearInvocations(inputMethodManager)
-        position = Offset(3f, 3f)
+        textLayoutOffset = Offset(3f, 3f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -266,7 +273,7 @@
         )
 
         // Monitoring update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected2 =
             builder.build(
                 textFieldValue,
@@ -285,11 +292,9 @@
             TextFieldValue("abc", selection = TextRange(2), composition = TextRange(1, 2))
         textInputService.updateState(oldValue = textFieldValue, newValue = textFieldValue)
 
-        position = Offset(1f, 1f)
+        textLayoutOffset = Offset(1f, 1f)
         val offsetMapping = OffsetMapping.Identity
         val textLayoutResult = getTextLayoutResult(textFieldValue.text)
-        val innerTextFieldBounds = Rect.Zero
-        val decorationBoxBounds = Rect.Zero
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -303,7 +308,7 @@
         )
 
         // Immediate update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected =
             builder.build(
                 textFieldValue,
@@ -316,7 +321,7 @@
         verify(inputMethodManager).updateCursorAnchorInfo(expected)
 
         clearInvocations(inputMethodManager)
-        position = Offset(2f, 2f)
+        textLayoutOffset = Offset(2f, 2f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -326,7 +331,7 @@
         )
 
         // Monitoring update
-        androidMatrix.setTranslate(position)
+        androidMatrix.setTranslate(textLayoutOffset)
         val expected2 =
             builder.build(
                 textFieldValue,
@@ -345,11 +350,9 @@
             TextFieldValue("abc", selection = TextRange(2), composition = TextRange(1, 2))
         textInputService.updateState(oldValue = textFieldValue, newValue = textFieldValue)
 
-        position = Offset(1f, 1f)
+        textLayoutOffset = Offset(1f, 1f)
         val offsetMapping = OffsetMapping.Identity
         val textLayoutResult = getTextLayoutResult(textFieldValue.text)
-        val innerTextFieldBounds = Rect.Zero
-        val decorationBoxBounds = Rect.Zero
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
@@ -371,7 +374,7 @@
         // No immediate update
         verify(inputMethodManager, never()).updateCursorAnchorInfo(any())
 
-        position = Offset(2f, 2f)
+        textLayoutOffset = Offset(2f, 2f)
         textInputService.updateTextLayoutResult(
             textFieldValue = textFieldValue,
             offsetMapping = offsetMapping,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerContextMenuTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerContextMenuTest.kt
index b050f4f..fb0f59d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerContextMenuTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerContextMenuTest.kt
@@ -117,11 +117,17 @@
         expectedClipboardContent = "Text",
     )
 
+    @Test
+    fun contextMenu_onClickSelectAll() = runClickContextMenuItemTest(
+        labelToClick = ContextMenuItemLabels.SELECT_ALL,
+        expectedSelection = TextRange(0, 14),
+    )
+
     @Suppress("SameParameterValue")
     private fun runClickContextMenuItemTest(
         labelToClick: String,
         expectedSelection: TextRange,
-        expectedClipboardContent: String,
+        expectedClipboardContent: String? = null,
     ) {
         val initialClipboardText = "clip"
 
@@ -163,7 +169,8 @@
         assertThat(selection!!.toTextRange()).isEqualTo(expectedSelection)
         val clipboardContent = clipboardManager.getText()
         assertThat(clipboardContent).isNotNull()
-        assertThat(clipboardContent!!.text).isEqualTo(expectedClipboardContent)
+        assertThat(clipboardContent!!.text)
+            .isEqualTo(expectedClipboardContent ?: initialClipboardText)
     }
 
     // endregion Context Menu Item Click Tests
@@ -178,7 +185,7 @@
             cutState = ContextMenuItemState.DOES_NOT_EXIST,
             copyState = ContextMenuItemState.DISABLED,
             pasteState = ContextMenuItemState.DOES_NOT_EXIST,
-            selectAllState = ContextMenuItemState.DOES_NOT_EXIST,
+            selectAllState = ContextMenuItemState.ENABLED,
         )
     }
 
@@ -192,7 +199,7 @@
             cutState = ContextMenuItemState.DOES_NOT_EXIST,
             copyState = ContextMenuItemState.ENABLED,
             pasteState = ContextMenuItemState.DOES_NOT_EXIST,
-            selectAllState = ContextMenuItemState.DOES_NOT_EXIST,
+            selectAllState = ContextMenuItemState.ENABLED,
         )
     }
 
@@ -206,7 +213,7 @@
             cutState = ContextMenuItemState.DOES_NOT_EXIST,
             copyState = ContextMenuItemState.ENABLED,
             pasteState = ContextMenuItemState.DOES_NOT_EXIST,
-            selectAllState = ContextMenuItemState.DOES_NOT_EXIST,
+            selectAllState = ContextMenuItemState.DISABLED,
         )
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
index 6c4fadc..f4e77b8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
@@ -168,7 +168,7 @@
         assertTextToolbarTopAt(pointerAreaRect.top)
 
         scrollLines(fromLine = 5, toLine = 3)
-        assertThat(textToolbarShown).isFalse()
+        assertTextToolbarTopAt(pointerAreaRect.top)
 
         scrollLines(fromLine = 5, toLine = 7)
         assertThat(textToolbarShown).isTrue()
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
index 186bdd2..7d2e1e9 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
@@ -20,8 +20,11 @@
 import android.view.View
 import android.widget.Magnifier
 import androidx.annotation.ChecksSdkIntAtLeast
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.withFrameMillis
 import androidx.compose.ui.Modifier
@@ -39,7 +42,6 @@
 import androidx.compose.ui.node.requireDensity
 import androidx.compose.ui.node.requireView
 import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.SemanticsPropertyKey
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.unit.Density
@@ -48,6 +50,7 @@
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.toSize
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
 
 /**
@@ -263,7 +266,7 @@
     private var view: View? = null
 
     /**
-     * Current density provided by [LocalDensity]. Used as a receiver to callback functions that
+     * Current density provided by [requireDensity]. Used as a receiver to callback functions that
      * are expected return pixel targeted offsets.
      */
     private var density: Density? = null
@@ -274,9 +277,28 @@
     private var magnifier: PlatformMagnifier? = null
 
     /**
-     * Anchor Composable's position in root layout.
+     * The latest [LayoutCoordinates] that is reported by [onGloballyPositioned] callback. Using
+     * [neverEqualPolicy] guarantees that every update to this value restarts snapshots aware
+     * listeners since the [LayoutCoordinates] instance itself does not change.
      */
-    private var anchorPositionInRoot: Offset by mutableStateOf(Offset.Unspecified)
+    private var layoutCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy())
+
+    /**
+     * Lazily initialized state that keeps track of anchor Composable's position in root layout.
+     * This state should be derived from [layoutCoordinates]. This variable shouldn't be used
+     * directly from the code, only [anchorPositionInRoot] should initialize and read from this.
+     */
+    private var anchorPositionInRootState: State<Offset>? = null
+
+    private val anchorPositionInRoot: Offset
+        get() {
+            if (anchorPositionInRootState == null) {
+                anchorPositionInRootState = derivedStateOf {
+                    layoutCoordinates?.positionInRoot() ?: Offset.Unspecified
+                }
+            }
+            return anchorPositionInRootState?.value ?: Offset.Unspecified
+        }
 
     /**
      * Position where [sourceCenter] is mapped on root layout. This is passed to platform magnifier
@@ -290,6 +312,8 @@
      */
     private var previousSize: IntSize? = null
 
+    private var drawSignalChannel: Channel<Unit>? = null
+
     fun update(
         sourceCenter: Density.() -> Offset,
         magnifierCenter: (Density.() -> Offset)?,
@@ -305,9 +329,12 @@
         val previousZoom = this.zoom
         val previousSize = this.size
         val previousCornerRadius = this.cornerRadius
+        val previousUseTextDefault = this.useTextDefault
         val previousElevation = this.elevation
         val previousClippingEnabled = this.clippingEnabled
         val previousPlatformMagnifierFactory = this.platformMagnifierFactory
+        val previousView = this.view
+        val previousDensity = this.density
 
         this.sourceCenter = sourceCenter
         this.magnifierCenter = magnifierCenter
@@ -320,26 +347,45 @@
         this.>
         this.platformMagnifierFactory = platformMagnifierFactory
 
-        // On platforms >=Q, the zoom level can be updated dynamically on an existing magnifier, so
-        // if the zoom changes between recompositions we don't need to recreate the magnifier. On
-        // older platforms, the zoom can only be set initially, so we use the zoom itself as a key
-        // so the magnifier gets recreated if it changes.
-        if (
-            magnifier == null ||
-            (zoom != previousZoom && !platformMagnifierFactory.canUpdateZoom) ||
-            size != previousSize ||
-            cornerRadius != previousCornerRadius ||
-            elevation != previousElevation ||
-            clippingEnabled != previousClippingEnabled ||
-            platformMagnifierFactory != previousPlatformMagnifierFactory
-        ) {
+        val view = requireView()
+        val density = requireDensity()
+
+        val shouldRecreate = magnifier != null && // only recreate if it was already created
+            // On platforms >=Q, the zoom level can be updated dynamically on an existing magnifier,
+            // so if the zoom changes between recompositions we don't need to recreate the
+            // magnifier. On older platforms, the zoom can only be set initially, so we use the
+            // zoom itself as a key so the magnifier gets recreated if it changes.
+            ((!zoom.equalsIncludingNaN(previousZoom) && !platformMagnifierFactory.canUpdateZoom) ||
+                size != previousSize ||
+                cornerRadius != previousCornerRadius ||
+                elevation != previousElevation ||
+                useTextDefault != previousUseTextDefault ||
+                clippingEnabled != previousClippingEnabled ||
+                platformMagnifierFactory != previousPlatformMagnifierFactory ||
+                view != previousView ||
+                density != previousDensity)
+
+        if (shouldRecreate) {
             recreateMagnifier()
         }
+
         updateMagnifier()
     }
 
     override fun onAttach() {
         onObservedReadsChanged()
+        drawSignalChannel = Channel()
+        coroutineScope.launch {
+            while (true) {
+                drawSignalChannel?.receive()
+                // don't update the magnifier immediately, actual frame draw happens right after
+                // all draw commands are recorded. Magnifier update should happen in the next frame.
+                if (magnifier != null) {
+                    withFrameMillis { }
+                    magnifier?.updateContent()
+                }
+            }
+        }
     }
 
     override fun onDetach() {
@@ -349,23 +395,14 @@
 
     override fun onObservedReadsChanged() {
         observeReads {
-            val previousView = view
-            val view = requireView().also { this.view = it }
-            val previousDensity = density
-            val density = requireDensity().also { this.density = it }
-
-            if (magnifier == null || view != previousView || density != previousDensity) {
-                recreateMagnifier()
-            }
-
             updateMagnifier()
         }
     }
 
     private fun recreateMagnifier() {
         magnifier?.dismiss()
-        val view = view ?: return
-        val density = density ?: return
+        val view = (view ?: requireView()).also { view = it }
+        val density = (density ?: requireDensity()).also { density = it }
         magnifier = platformMagnifierFactory.create(
             view = view,
             useTextDefault = useTextDefault,
@@ -380,37 +417,38 @@
     }
 
     private fun updateMagnifier() {
-        val magnifier = magnifier ?: return
-        val density = density ?: return
+        val density = density ?: requireDensity().also { density = it }
 
         val sourceCenterOffset = sourceCenter(density)
-        sourceCenterInRoot =
-            if (anchorPositionInRoot.isSpecified && sourceCenterOffset.isSpecified) {
-                anchorPositionInRoot + sourceCenterOffset
-            } else {
-                Offset.Unspecified
-            }
 
-        // Once the position is set, it's never null again, so we don't need to worry
-        // about dismissing the magnifier if this expression changes value.
-        if (sourceCenterInRoot.isSpecified) {
-            // Calculate magnifier center if it's provided. Only accept if the returned value is
-            // specified. Then add [anchorPositionInRoot] for relative positioning.
+        // the order of these checks are important since we don't want to query
+        // `anchorPositionInRoot` if `sourceCenterOffset` is unspecified.
+        if (sourceCenterOffset.isSpecified && anchorPositionInRoot.isSpecified) {
+            sourceCenterInRoot = anchorPositionInRoot + sourceCenterOffset
+            // Calculate magnifier center if it's provided. Only accept if the returned
+            // value is specified. Then add [anchorPositionInRoot] for relative positioning.
             val magnifierCenter = magnifierCenter?.invoke(density)
                 ?.takeIf { it.isSpecified }
                 ?.let { anchorPositionInRoot + it }
                 ?: Offset.Unspecified
 
-            magnifier.update(
+            if (magnifier == null) {
+                recreateMagnifier()
+            }
+
+            magnifier?.update(
                 sourceCenter = sourceCenterInRoot,
                 magnifierCenter = magnifierCenter,
                 zoom = zoom
             )
             updateSizeIfNecessary()
-        } else {
-            // Can't place the magnifier at an unspecified location, so just hide it.
-            magnifier.dismiss()
+            return
         }
+
+        // If the flow reaches here, it means that the magnifier could not be placed at a specified
+        // position. We now need to hide it so it doesn't show up at an invalid location.
+        sourceCenterInRoot = Offset.Unspecified
+        magnifier?.dismiss()
     }
 
     private fun updateSizeIfNecessary() {
@@ -425,19 +463,14 @@
 
     override fun ContentDrawScope.draw() {
         drawContent()
-        // don't update the magnifier immediately, actual frame draw happens right after all draw
-        // commands are recorded. Magnifier update should happen in the next frame.
-        coroutineScope.launch {
-            withFrameMillis { }
-            magnifier?.updateContent()
-        }
+        drawSignalChannel?.trySend(Unit)
     }
 
     override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
         // The mutable state must store the Offset, not the LocalCoordinates, because the same
         // LocalCoordinates instance may be sent to this callback multiple times, not implement
         // equals, or be stable, and so won't invalidate the snapshotFlow.
-        anchorPositionInRoot = coordinates.positionInRoot()
+        layoutCoordinates = coordinates
     }
 
     override fun SemanticsPropertyReceiver.applySemantics() {
@@ -448,3 +481,13 @@
 @ChecksSdkIntAtLeast(api = 28)
 internal fun isPlatformMagnifierSupported(sdkVersion: Int = Build.VERSION.SDK_INT) =
     sdkVersion >= 28
+
+/**
+ * Normally `Float.NaN == Float.NaN` returns false but we use [Float.NaN] to mean Unspecified.
+ * The comparison between two unspecified values should return _equal_ if we are only interested
+ * in state changes.
+ */
+internal fun Float.equalsIncludingNaN(other: Float): Boolean {
+    if (this.isNaN() && other.isNaN()) return true
+    return this == other
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.android.kt
index 54abefd..fd93543 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.android.kt
@@ -126,16 +126,19 @@
         }
         val latestFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(view.drawingTime)
         val nextFrameNs = latestFrameVsyncNs + frameIntervalNs
-        val  > nextFrameNs
-        val scope = PrefetchRequestScopeImpl(nextFrameNs, oneOverTimeTaskAllowed)
+        val scope = PrefetchRequestScopeImpl(nextFrameNs)
         var scheduleForNextFrame = false
         while (prefetchRequests.isNotEmpty() && !scheduleForNextFrame) {
-            val request = prefetchRequests[0]
-            val hasMoreWorkToDo = with(request) { scope.execute() }
-            if (hasMoreWorkToDo) {
-                scheduleForNextFrame = true
+            if (scope.availableTimeNanos() > 0) {
+                val request = prefetchRequests[0]
+                val hasMoreWorkToDo = with(request) { scope.execute() }
+                if (hasMoreWorkToDo) {
+                    scheduleForNextFrame = true
+                } else {
+                    prefetchRequests.removeAt(0)
+                }
             } else {
-                prefetchRequests.removeAt(0)
+                scheduleForNextFrame = true
             }
         }
 
@@ -182,24 +185,10 @@
 
     class PrefetchRequestScopeImpl(
         private val nextFrameTimeNs: Long,
-        isOneOverTimeTaskAllowed: Boolean
     ) : PrefetchRequestScope {
 
-        private var canDoOverTimeTask = isOneOverTimeTaskAllowed
-
-        override val availableTimeNanos: Long
-            get() {
-                // This logic is meant to be temporary until we replace the isOneOverTimeTaskAllowed
-                // logic with something more general. For now, we assume that a PrefetchRequest
-                // impl will check availableTimeNanos once per task and we give it a large amount
-                // of time the first time it checks if we allow an overtime task.
-                return if (canDoOverTimeTask) {
-                    canDoOverTimeTask = false
-                    Long.MAX_VALUE
-                } else {
-                    max(0, nextFrameTimeNs - System.nanoTime())
-                }
-            }
+        override fun availableTimeNanos() =
+            max(0, nextFrameTimeNs - System.nanoTime())
     }
 
     companion object {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
index 1bdf683..a53b03d 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
@@ -203,5 +203,11 @@
             InsertGesture::class.java,
             RemoveSpaceGesture::class.java
         )
+        editorInfo.supportedHandwritingGesturePreviews = setOf(
+            SelectGesture::class.java,
+            DeleteGesture::class.java,
+            SelectRangeGesture::class.java,
+            DeleteRangeGesture::class.java
+        )
     }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
index d47c722..ba0422e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
@@ -421,6 +421,30 @@
     }
 
     @DoNotInline
+    internal fun LegacyTextFieldState.previewHandwritingGesture(
+        gesture: PreviewableHandwritingGesture,
+        textFieldSelectionManager: TextFieldSelectionManager?,
+        cancellationSignal: CancellationSignal?
+    ): Boolean {
+        val text = untransformedText ?: return false
+        if (text != layoutResult?.value?.layoutInput?.text) {
+            // The text is transformed or layout is null, handwriting gesture failed.
+            return false
+        }
+        when (gesture) {
+            is SelectGesture -> previewSelectGesture(gesture, textFieldSelectionManager)
+            is DeleteGesture -> previewDeleteGesture(gesture, textFieldSelectionManager)
+            is SelectRangeGesture -> previewSelectRangeGesture(gesture, textFieldSelectionManager)
+            is DeleteRangeGesture -> previewDeleteRangeGesture(gesture, textFieldSelectionManager)
+            else -> return false
+        }
+        cancellationSignal?.setOnCancelListener {
+            textFieldSelectionManager?.clearPreviewHighlight()
+        }
+        return true
+    }
+
+    @DoNotInline
     private fun LegacyTextFieldState.performSelectGesture(
         gesture: SelectGesture,
         textSelectionManager: TextFieldSelectionManager?,
@@ -439,6 +463,20 @@
     }
 
     @DoNotInline
+    private fun LegacyTextFieldState.previewSelectGesture(
+        gesture: SelectGesture,
+        textFieldSelectionManager: TextFieldSelectionManager?
+    ) {
+        textFieldSelectionManager?.setSelectionPreviewHighlight(
+            getRangeForScreenRect(
+                gesture.selectionArea.toComposeRect(),
+                gesture.granularity.toTextGranularity(),
+                TextInclusionStrategy.ContainsCenter
+            )
+        )
+    }
+
+    @DoNotInline
     private fun LegacyTextFieldState.performDeleteGesture(
         gesture: DeleteGesture,
         text: AnnotatedString,
@@ -463,6 +501,20 @@
     }
 
     @DoNotInline
+    private fun LegacyTextFieldState.previewDeleteGesture(
+        gesture: DeleteGesture,
+        textFieldSelectionManager: TextFieldSelectionManager?
+    ) {
+        textFieldSelectionManager?.setDeletionPreviewHighlight(
+            getRangeForScreenRect(
+                gesture.deletionArea.toComposeRect(),
+                gesture.granularity.toTextGranularity(),
+                TextInclusionStrategy.ContainsCenter
+            )
+        )
+    }
+
+    @DoNotInline
     private fun LegacyTextFieldState.performSelectRangeGesture(
         gesture: SelectRangeGesture,
         textSelectionManager: TextFieldSelectionManager?,
@@ -486,6 +538,21 @@
     }
 
     @DoNotInline
+    private fun LegacyTextFieldState.previewSelectRangeGesture(
+        gesture: SelectRangeGesture,
+        textFieldSelectionManager: TextFieldSelectionManager?
+    ) {
+        textFieldSelectionManager?.setSelectionPreviewHighlight(
+            getRangeForScreenRects(
+                gesture.selectionStartArea.toComposeRect(),
+                gesture.selectionEndArea.toComposeRect(),
+                gesture.granularity.toTextGranularity(),
+                TextInclusionStrategy.ContainsCenter
+            )
+        )
+    }
+
+    @DoNotInline
     private fun LegacyTextFieldState.performDeleteRangeGesture(
         gesture: DeleteRangeGesture,
         text: AnnotatedString,
@@ -510,6 +577,21 @@
     }
 
     @DoNotInline
+    private fun LegacyTextFieldState.previewDeleteRangeGesture(
+        gesture: DeleteRangeGesture,
+        textFieldSelectionManager: TextFieldSelectionManager?
+    ) {
+        textFieldSelectionManager?.setDeletionPreviewHighlight(
+            getRangeForScreenRects(
+                gesture.deletionStartArea.toComposeRect(),
+                gesture.deletionEndArea.toComposeRect(),
+                gesture.granularity.toTextGranularity(),
+                TextInclusionStrategy.ContainsCenter
+            )
+        )
+    }
+
+    @DoNotInline
     private fun LegacyTextFieldState.performJoinOrSplitGesture(
         gesture: JoinOrSplitGesture,
         text: AnnotatedString,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt
index 5702c47..6e0d405 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoBuilder.android.kt
@@ -37,11 +37,11 @@
  * @param textFieldValue the text field's [TextFieldValue]
  * @param offsetMapping the offset mapping for the text field's visual transformation
  * @param textLayoutResult the text field's [TextLayoutResult]
- * @param matrix matrix that transforms local coordinates into screen coordinates
- * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an empty
- *   rectangle if the text field is not visible
- * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an empty
- *   rectangle if the decoration box is not visible
+ * @param matrix matrix that transforms text layout coordinates into screen coordinates
+ * @param innerTextFieldBounds visible bounds of the text field in text layout coordinates, or an
+ *   empty rectangle if the text field is not visible
+ * @param decorationBoxBounds visible bounds of the decoration box in text layout coordinates, or an
+ *   empty rectangle if the decoration box is not visible
  * @param includeInsertionMarker whether to include insertion marker info in the CursorAnchorInfo
  * @param includeCharacterBounds whether to include character bounds info in the CursorAnchorInfo
  * @param includeEditorBounds whether to include editor bounds info in the CursorAnchorInfo
@@ -127,8 +127,8 @@
         flags = flags or CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION
     if (isRtl) flags = flags or CursorAnchorInfo.FLAG_IS_RTL
 
-    // Sets the location of the text insertion point (zero width cursor) as a rectangle in local
-    // coordinates.
+    // Sets the location of the text insertion point (zero width cursor) as a rectangle in text
+    // layout coordinates.
     setInsertionMarkerLocation(x, cursorRect.top, cursorRect.bottom, cursorRect.bottom, flags)
 
     return this
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt
index bda500a..fdc08c8 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyCursorAnchorInfoController.android.kt
@@ -93,10 +93,10 @@
      * @param textFieldValue the text field's [TextFieldValue]
      * @param offsetMapping the offset mapping for the visual transformation
      * @param textLayoutResult the text field's [TextLayoutResult]
-     * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an
-     *   empty rectangle if the text field is not visible
-     * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an
-     *   empty rectangle if the decoration box is not visible
+     * @param innerTextFieldBounds visible bounds of the text field in text layout coordinates,
+     *   an empty rectangle if the text field is not visible
+     * @param decorationBoxBounds visible bounds of the decoration box in text layout coordinates,
+     *   or an empty rectangle if the decoration box is not visible
      */
     fun updateTextLayoutResult(
         textFieldValue: TextFieldValue,
@@ -135,8 +135,10 @@
         if (!inputMethodManager.isActive()) return
 
         matrix.reset()
-        // Updates matrix to transform text field local coordinates to screen coordinates.
+        // Updates matrix to transform decoration box coordinates to screen coordinates.
         localToScreen(matrix)
+        // Updates matrix to transform text layout coordinates to screen coordinates.
+        matrix.translate(-decorationBoxBounds!!.left, -decorationBoxBounds!!.top, 0f)
         androidMatrix.setFrom(matrix)
 
         inputMethodManager.updateCursorAnchorInfo(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
index 4d897e0..34e71cb 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/RecordingInputConnection.android.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import android.os.Bundle
+import android.os.CancellationSignal
 import android.os.Handler
 import android.text.TextUtils
 import android.util.Log
@@ -30,10 +31,12 @@
 import android.view.inputmethod.HandwritingGesture
 import android.view.inputmethod.InputConnection
 import android.view.inputmethod.InputContentInfo
+import android.view.inputmethod.PreviewableHandwritingGesture
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.foundation.text.LegacyTextFieldState
 import androidx.compose.foundation.text.input.internal.HandwritingGestureApi34.performHandwritingGesture
+import androidx.compose.foundation.text.input.internal.HandwritingGestureApi34.previewHandwritingGesture
 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.text.input.CommitTextCommand
@@ -426,6 +429,22 @@
         }
     }
 
+    override fun previewHandwritingGesture(
+        gesture: PreviewableHandwritingGesture,
+        cancellationSignal: CancellationSignal?
+    ): Boolean {
+        if (DEBUG) { logDebug("previewHandwritingGesture($gesture, $cancellationSignal)") }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            return Api34LegacyPerformHandwritingGestureImpl.previewHandwritingGesture(
+                legacyTextFieldState,
+                textFieldSelectionManager,
+                gesture,
+                cancellationSignal
+            )
+        }
+        return false
+    }
+
     // endregion
     // region Unsupported callbacks
 
@@ -533,4 +552,18 @@
             consumer.accept(result)
         }
     }
+
+    @DoNotInline
+    fun previewHandwritingGesture(
+        legacyTextFieldState: LegacyTextFieldState?,
+        textFieldSelectionManager: TextFieldSelectionManager?,
+        gesture: PreviewableHandwritingGesture,
+        cancellationSignal: CancellationSignal?
+    ): Boolean {
+        return legacyTextFieldState?.previewHandwritingGesture(
+            gesture,
+            textFieldSelectionManager,
+            cancellationSignal
+        ) ?: false
+    }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
index 19d75d2..4f24fb9 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
@@ -78,6 +78,7 @@
     state: ContextMenuState,
 ): ContextMenuScope.() -> Unit {
     val copyString = TextContextMenuItems.Copy.resolvedString()
+    val selectAllString = TextContextMenuItems.SelectAll.resolvedString()
     return {
         listOf(
             item(
@@ -88,7 +89,14 @@
                     state.close()
                 },
             ),
-            // TODO(b/240143283) Add select all item
+            item(
+                label = selectAllString,
+                enabled = !isEntireContainerSelected(),
+                >
+                    selectAll()
+                    state.close()
+                },
+            ),
         )
     }
 }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
index 98160e5..ca6265d 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
@@ -282,7 +282,7 @@
     var boundingBoxes: Map<Int, Rect> = emptyMap()
 
     private val selectableKey = 1L
-    private val fakeSelectAllSelection: Selection = Selection(
+    var fakeSelectAllSelection: Selection? = Selection(
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
@@ -309,7 +309,7 @@
         )
     }
 
-    override fun getSelectAllSelection(): Selection {
+    override fun getSelectAllSelection(): Selection? {
         return fakeSelectAllSelection
     }
 
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
index a1b2d08..085643e 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
@@ -16,16 +16,21 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.collection.LongObjectMap
 import androidx.collection.emptyLongObjectMap
 import androidx.collection.longObjectMapOf
+import androidx.collection.mutableLongObjectMapOf
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.ClipboardManager
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.util.fastForEach
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.fail
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -211,14 +216,14 @@
     }
 
     @Test
-    fun mergeSelections_selectAll() {
+    fun mergeSelections_selectAllInSelectable() {
         val anotherSelectableId = 100L
         val selectableAnother = mock<Selectable>()
         whenever(selectableAnother.selectableId).thenReturn(anotherSelectableId)
 
         selectionRegistrar.subscribe(selectableAnother)
 
-        selectionManager.selectAll(
+        selectionManager.selectAllInSelectable(
             selectableId = selectableId,
             previousSelection = fakeSelection
         )
@@ -805,7 +810,7 @@
             any(),
             isNull(),
             isNull(),
-            isNull()
+            any()
         )
     }
 
@@ -911,4 +916,362 @@
             times(1)
         ).performHapticFeedback(HapticFeedbackType.TextHandleMove)
     }
+
+    // region isEntireContainerSelected Tests
+    @Test
+    fun isEntireContainerSelected_noSelectables_returnsTrue() {
+        isEntireContainerSelectedTest(expectedResult = true)
+    }
+
+    @Test
+    fun isEntireContainerSelected_singleEmptySelectable_returnsTrue() {
+        isEntireContainerSelectedTest(
+            expectedResult = true,
+            IsEntireContainerSelectedData(text = "", selection = null),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_multipleEmptySelectables_returnsTrue() {
+        isEntireContainerSelectedTest(
+            expectedResult = true,
+            IsEntireContainerSelectedData(text = "", selection = null),
+            IsEntireContainerSelectedData(text = "", selection = null),
+            IsEntireContainerSelectedData(text = "", selection = null),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_emptySurroundingNonEmpty_fullySelected_returnsTrue() {
+        isEntireContainerSelectedTest(
+            expectedResult = true,
+            IsEntireContainerSelectedData(text = "", selection = null),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "", selection = null),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_nonEmptySurroundingEmpty_fullySelected_returnsTrue() {
+        isEntireContainerSelectedTest(
+            expectedResult = true,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "", selection = TextRange(0, 0)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_nonEmptyFirstTextNotSelected_returnsFalse() {
+        isEntireContainerSelectedTest(
+            expectedResult = false,
+            IsEntireContainerSelectedData(text = "Text", selection = null),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_nonEmptyLastTextNotSelected_returnsFalse() {
+        isEntireContainerSelectedTest(
+            expectedResult = false,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = null),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_firstTextPartiallySelected_returnsFalse() {
+        isEntireContainerSelectedTest(
+            expectedResult = false,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(1, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_lastTextPartiallySelected_returnsFalse() {
+        isEntireContainerSelectedTest(
+            expectedResult = false,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 4)),
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(0, 3)),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_reversedSelectionFullySelected_returnsTrue() {
+        isEntireContainerSelectedTest(
+            expectedResult = true,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(4, 0)),
+        )
+    }
+
+    @Test
+    fun isEntireContainerSelected_reversedSelectionPartiallySelected_returnsFalse() {
+        isEntireContainerSelectedTest(
+            expectedResult = false,
+            IsEntireContainerSelectedData(text = "Text", selection = TextRange(3, 0)),
+        )
+    }
+
+    /**
+     * Data necessary to set up a [SelectionManager.isEntireContainerSelected] unit test.
+     *
+     * @param text The text for the [Selectable] to return in [Selectable.getText].
+     * @param selection The selection to be associated with the [SelectionRegistrar.subselections].
+     * Null implies "do not include this selectable in the sub-selection".
+     */
+    private data class IsEntireContainerSelectedData(
+        val text: String,
+        val selection: TextRange?,
+    )
+
+    private fun isEntireContainerSelectedTest(
+        expectedResult: Boolean,
+        vararg selectableStates: IsEntireContainerSelectedData,
+    ) {
+        val selectables = selectableStates.mapIndexed { index, item ->
+            FakeSelectable().apply {
+                selectableId = index + 1L
+                textToReturn = AnnotatedString(item.text)
+            }
+        }
+
+        val registrar = SelectionRegistrarImpl().apply {
+            selectables.fastForEach { subscribe(it) }
+            subselections = selectableStates
+                .withIndex()
+                .filter { it.value.selection != null }
+                .associate { (index, item) ->
+                    val id = index + 1L
+                    val selection = item.selection
+                    id to Selection(
+                        start = Selection.AnchorInfo(
+                            direction = ResolvedTextDirection.Ltr,
+                            offset = selection!!.start,
+                            selectableId = id
+                        ),
+                        end = Selection.AnchorInfo(
+                            direction = ResolvedTextDirection.Ltr,
+                            offset = selection.end,
+                            selectableId = id
+                        ),
+                        handlesCrossed = selection.reversed
+                    )
+                }
+                .toLongObjectMap()
+        }
+
+        val manager = SelectionManager(registrar).apply {
+            containerLayoutCoordinates = MockCoordinates()
+        }
+
+        assertThat(manager.isEntireContainerSelected()).run {
+            if (expectedResult) isTrue() else isFalse()
+        }
+    }
+    // endregion isEntireContainerSelected Tests
+
+    // region selectAll Tests
+    @Test
+    fun selectAll_noSelectables_noSelection() {
+        selectAllTest(
+            expectedSelection = null,
+            expectedSubSelectionRanges = emptyMap(),
+        )
+    }
+
+    @Test
+    fun selectAll_singleUnSelectable_noSelection() {
+        selectAllTest(
+            expectedSelection = null,
+            expectedSubSelectionRanges = emptyMap(),
+            SelectAllData(text = "Text", selection = null),
+        )
+    }
+
+    @Test
+    fun selectAll_singleSelectable_selectedAsExpected() {
+        selectAllTest(
+            expectedSelection = expectedSelection(0, 4),
+            expectedSubSelectionRanges = mapOf(1L to TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun selectAll_multiSelectable_selectedAsExpected() {
+        selectAllTest(
+            expectedSelection = expectedSelection(
+                startOffset = 0,
+                endOffset = 4,
+                startSelectableId = 1L,
+                endSelectableId = 3L,
+            ),
+            expectedSubSelectionRanges = mapOf(
+                1L to TextRange(0, 4),
+                2L to TextRange(0, 4),
+                3L to TextRange(0, 4),
+            ),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun selectAll_multiSelectable_skipFirst_selectedAsExpected() {
+        selectAllTest(
+            expectedSelection = expectedSelection(
+                startOffset = 0,
+                endOffset = 4,
+                startSelectableId = 2L,
+                endSelectableId = 3L,
+            ),
+            expectedSubSelectionRanges = mapOf(
+                2L to TextRange(0, 4),
+                3L to TextRange(0, 4),
+            ),
+            SelectAllData(text = "Text", selection = null),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun selectAll_multiSelectable_skipMiddle_selectedAsExpected() {
+        selectAllTest(
+            expectedSelection = expectedSelection(
+                startOffset = 0,
+                endOffset = 4,
+                startSelectableId = 1L,
+                endSelectableId = 3L,
+            ),
+            expectedSubSelectionRanges = mapOf(
+                1L to TextRange(0, 4),
+                3L to TextRange(0, 4),
+            ),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = null),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+        )
+    }
+
+    @Test
+    fun selectAll_multiSelectable_skipLast_selectedAsExpected() {
+        selectAllTest(
+            expectedSelection = expectedSelection(
+                startOffset = 0,
+                endOffset = 4,
+                startSelectableId = 1L,
+                endSelectableId = 2L,
+            ),
+            expectedSubSelectionRanges = mapOf(
+                1L to TextRange(0, 4),
+                2L to TextRange(0, 4),
+            ),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = TextRange(0, 4)),
+            SelectAllData(text = "Text", selection = null),
+        )
+    }
+
+    private fun expectedSelection(
+        startOffset: Int,
+        endOffset: Int,
+        startSelectableId: Long = 1L,
+        endSelectableId: Long = 1L,
+        handlesCrossed: Boolean = false,
+    ): Selection = Selection(
+        start = Selection.AnchorInfo(
+            direction = ResolvedTextDirection.Ltr,
+            offset = startOffset,
+            selectableId = startSelectableId
+        ),
+        end = Selection.AnchorInfo(
+            direction = ResolvedTextDirection.Ltr,
+            offset = endOffset,
+            selectableId = endSelectableId
+        ),
+        handlesCrossed = handlesCrossed
+    )
+
+    /**
+     * Data necessary to set up a [SelectionManager.selectAll] unit test.
+     *
+     * @param text The text for the [FakeSelectable] to return in [Selectable.getText].
+     * @param selection The selection for the [FakeSelectable] to return in
+     * [Selectable.getSelectAllSelection].
+     */
+    private data class SelectAllData(
+        val text: String,
+        val selection: TextRange?,
+    )
+
+    private fun selectAllTest(
+        expectedSelection: Selection?,
+        expectedSubSelectionRanges: Map<Long, TextRange>,
+        vararg selectableStates: SelectAllData,
+    ) {
+        val selectables = selectableStates.mapIndexed { index, item ->
+            val id = index + 1L
+            val range = item.selection
+            FakeSelectable().apply {
+                selectableId = id
+                textToReturn = AnnotatedString(item.text)
+                fakeSelectAllSelection = range?.let {
+                    Selection(
+                        start = Selection.AnchorInfo(
+                            direction = ResolvedTextDirection.Ltr,
+                            offset = it.start,
+                            selectableId = id
+                        ),
+                        end = Selection.AnchorInfo(
+                            direction = ResolvedTextDirection.Ltr,
+                            offset = it.end,
+                            selectableId = id
+                        ),
+                        handlesCrossed = it.reversed
+                    )
+                }
+            }
+        }
+
+        val registrar = SelectionRegistrarImpl().apply {
+            selectables.fastForEach { subscribe(it) }
+        }
+
+        val expectedSubSelections = expectedSubSelectionRanges.mapValues { (id, range) ->
+            expectedSelection(
+                startOffset = range.start,
+                endOffset = range.end,
+                startSelectableId = id,
+                endSelectableId = id,
+                handlesCrossed = range.start > range.end
+            )
+        }
+            .toLongObjectMap()
+
+        SelectionManager(registrar).apply {
+            containerLayoutCoordinates = MockCoordinates()
+             newSelection ->
+                if (expectedSelection == null) {
+                    fail("Expected no selection update, but received one anyways.")
+                }
+                assertThat(newSelection).isEqualTo(expectedSelection)
+            }
+            selectAll()
+        }
+
+        assertThat(registrar.subselections).isEqualTo(expectedSubSelections)
+    }
+    // endregion selectAll Tests
+
+    private fun <T> Map<Long, T>.toLongObjectMap(): LongObjectMap<T> =
+        mutableLongObjectMapOf<T>().apply {
+            this@toLongObjectMap.keys.forEach { key -> put(key, this@toLongObjectMap[key]!!) }
+        }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
index 20a8d5f..556bcb0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
@@ -42,10 +42,11 @@
     }
 
     override fun calculateDistanceTo(targetIndex: Int): Float {
-        val visibleItem =
-            state.layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
+        val layoutInfo = state.layoutInfo
+        if (layoutInfo.visibleItemsInfo.isEmpty()) return 0f
+        val visibleItem = layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
         return if (visibleItem == null) {
-            val averageSize = visibleItemsAverageSize
+            val averageSize = calculateVisibleItemsAverageSize(layoutInfo)
             val indexesDiff = targetIndex - firstVisibleItemIndex
             (averageSize * indexesDiff).toFloat() - firstVisibleItemScrollOffset
         } else {
@@ -57,11 +58,9 @@
         state.scroll(block = block)
     }
 
-    private val visibleItemsAverageSize: Int
-        get() {
-            val layoutInfo = state.layoutInfo
-            val visibleItems = layoutInfo.visibleItemsInfo
-            val itemsSum = visibleItems.fastSumBy { it.size }
-            return itemsSum / visibleItems.size + layoutInfo.mainAxisItemSpacing
-        }
+    private fun calculateVisibleItemsAverageSize(layoutInfo: LazyListLayoutInfo): Int {
+        val visibleItems = layoutInfo.visibleItemsInfo
+        val itemsSum = visibleItems.fastSumBy { it.size }
+        return itemsSum / visibleItems.size + layoutInfo.mainAxisItemSpacing
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetchStrategy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetchStrategy.kt
index dc29f8c..83840e9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetchStrategy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetchStrategy.kt
@@ -147,21 +147,38 @@
             } else {
                 layoutInfo.visibleItemsInfo.first().index - 1
             }
-            if (indexToPrefetch != this@DefaultLazyListPrefetchStrategy.indexToPrefetch &&
-                indexToPrefetch in 0 until layoutInfo.totalItemsCount
-            ) {
-                if (wasScrollingForward != scrollingForward) {
-                    // the scrolling direction has been changed which means the last prefetched
-                    // is not going to be reached anytime soon so it is safer to dispose it.
-                    // if this item is already visible it is safe to call the method anyway
-                    // as it will be no-op
-                    currentPrefetchHandle?.cancel()
+            if (indexToPrefetch in 0 until layoutInfo.totalItemsCount) {
+                if (indexToPrefetch != this@DefaultLazyListPrefetchStrategy.indexToPrefetch) {
+                    if (wasScrollingForward != scrollingForward) {
+                        // the scrolling direction has been changed which means the last prefetched
+                        // is not going to be reached anytime soon so it is safer to dispose it.
+                        // if this item is already visible it is safe to call the method anyway
+                        // as it will be no-op
+                        currentPrefetchHandle?.cancel()
+                    }
+                    this@DefaultLazyListPrefetchStrategy.wasScrollingForward = scrollingForward
+                    this@DefaultLazyListPrefetchStrategy.indexToPrefetch = indexToPrefetch
+                    currentPrefetchHandle = schedulePrefetch(
+                        indexToPrefetch
+                    )
                 }
-                this@DefaultLazyListPrefetchStrategy.wasScrollingForward = scrollingForward
-                this@DefaultLazyListPrefetchStrategy.indexToPrefetch = indexToPrefetch
-                currentPrefetchHandle = schedulePrefetch(
-                    indexToPrefetch
-                )
+                if (scrollingForward) {
+                    val lastItem = layoutInfo.visibleItemsInfo.last()
+                    val spacing = layoutInfo.mainAxisItemSpacing
+                    val distanceToPrefetchItem =
+                        lastItem.offset + lastItem.size + spacing - layoutInfo.viewportEndOffset
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToPrefetchItem < -delta) {
+                        currentPrefetchHandle?.markAsUrgent()
+                    }
+                } else {
+                    val firstItem = layoutInfo.visibleItemsInfo.first()
+                    val distanceToPrefetchItem = layoutInfo.viewportStartOffset - firstItem.offset
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToPrefetchItem < delta) {
+                        currentPrefetchHandle?.markAsUrgent()
+                    }
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
index 78e6ac9..5fa07f2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
@@ -37,27 +37,25 @@
 
     override val itemCount: Int get() = state.layoutInfo.totalItemsCount
 
-    private val visibleItemsAverageSize: Int
-        get() = calculateLineAverageMainAxisSize(state.layoutInfo)
-
     override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
         state.snapToItemIndexInternal(index, scrollOffset, forceRemeasure = true)
     }
 
     override fun calculateDistanceTo(targetIndex: Int): Float {
-        val visibleItem =
-            state.layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
+        val layoutInfo = state.layoutInfo
+        if (layoutInfo.visibleItemsInfo.isEmpty()) return 0f
+        val visibleItem = layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
 
         return if (visibleItem == null) {
             val slotsPerLine = state.slotsPerLine
-            val averageLineMainAxisSize = visibleItemsAverageSize
+            val averageLineMainAxisSize = calculateLineAverageMainAxisSize(layoutInfo)
             val before = targetIndex < firstVisibleItemIndex
             val linesDiff =
                 (targetIndex - firstVisibleItemIndex + (slotsPerLine - 1) * if (before) -1 else 1) /
                     slotsPerLine
             (averageLineMainAxisSize * linesDiff).toFloat() - firstVisibleItemScrollOffset
         } else {
-            if (state.layoutInfo.orientation == Orientation.Vertical) {
+            if (layoutInfo.orientation == Orientation.Vertical) {
                 visibleItem.offset.y
             } else {
                 visibleItem.offset.x
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index ab042b2..d12d075 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.gestures.snapping.offsetOnMainAxis
 import androidx.compose.foundation.gestures.stopScroll
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -31,6 +32,7 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -81,19 +83,26 @@
  * A state object that can be hoisted to control and observe scrolling.
  *
  * In most cases, this will be created via [rememberLazyGridState].
- *
- * @param firstVisibleItemIndex the initial value for [LazyGridState.firstVisibleItemIndex]
- * @param firstVisibleItemScrollOffset the initial value for
- * [LazyGridState.firstVisibleItemScrollOffset]
  */
 @OptIn(ExperimentalFoundationApi::class)
 @Stable
-class LazyGridState constructor(
+class LazyGridState internal constructor(
     firstVisibleItemIndex: Int = 0,
-    firstVisibleItemScrollOffset: Int = 0
+    firstVisibleItemScrollOffset: Int = 0,
+    prefetchScheduler: PrefetchScheduler?,
 ) : ScrollableState {
 
     /**
+     * @param firstVisibleItemIndex the initial value for [LazyGridState.firstVisibleItemIndex]
+     * @param firstVisibleItemScrollOffset the initial value for
+     * [LazyGridState.firstVisibleItemScrollOffset]
+     */
+    constructor(
+        firstVisibleItemIndex: Int = 0,
+        firstVisibleItemScrollOffset: Int = 0
+    ) : this(firstVisibleItemIndex, firstVisibleItemScrollOffset, null)
+
+    /**
      * The holder class for the current scroll position.
      */
     private val scrollPosition =
@@ -413,23 +422,40 @@
                 }
                 closestNextItemToPrefetch = info.visibleItemsInfo.first().index - 1
             }
-            if (lineToPrefetch != this.lineToPrefetch &&
-                closestNextItemToPrefetch in 0 until info.totalItemsCount
-            ) {
-                if (wasScrollingForward != scrollingForward) {
-                    // the scrolling direction has been changed which means the last prefetched
-                    // is not going to be reached anytime soon so it is safer to dispose it.
-                    // if this line is already visible it is safe to call the method anyway
-                    // as it will be no-op
-                    currentLinePrefetchHandles.forEach { it.cancel() }
+            if (closestNextItemToPrefetch in 0 until info.totalItemsCount) {
+                if (lineToPrefetch != this.lineToPrefetch) {
+                    if (wasScrollingForward != scrollingForward) {
+                        // the scrolling direction has been changed which means the last prefetched
+                        // is not going to be reached anytime soon so it is safer to dispose it.
+                        // if this line is already visible it is safe to call the method anyway
+                        // as it will be no-op
+                        currentLinePrefetchHandles.forEach { it.cancel() }
+                    }
+                    this.wasScrollingForward = scrollingForward
+                    this.lineToPrefetch = lineToPrefetch
+                    currentLinePrefetchHandles.clear()
+                    info.prefetchInfoRetriever(lineToPrefetch).fastForEach {
+                        currentLinePrefetchHandles.add(
+                            prefetchState.schedulePrefetch(it.first, it.second)
+                        )
+                    }
                 }
-                this.wasScrollingForward = scrollingForward
-                this.lineToPrefetch = lineToPrefetch
-                currentLinePrefetchHandles.clear()
-                info.prefetchInfoRetriever(lineToPrefetch).fastForEach {
-                    currentLinePrefetchHandles.add(
-                        prefetchState.schedulePrefetch(it.first, it.second)
-                    )
+                if (scrollingForward) {
+                    val lastItem = info.visibleItemsInfo.last()
+                    val distanceToPrefetchItem = lastItem.offsetOnMainAxis(info.orientation) +
+                        lastItem.mainAxisSizeWithSpacings - info.viewportEndOffset
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToPrefetchItem < -delta) {
+                        currentLinePrefetchHandles.forEach { it.markAsUrgent() }
+                    }
+                } else {
+                    val firstItem = info.visibleItemsInfo.first()
+                    val distanceToPrefetchItem = info.viewportStartOffset -
+                        firstItem.offsetOnMainAxis(info.orientation)
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToPrefetchItem < delta) {
+                        currentLinePrefetchHandles.forEach { it.markAsUrgent() }
+                    }
                 }
             }
         }
@@ -454,7 +480,7 @@
         }
     }
 
-    internal val prefetchState = LazyLayoutPrefetchState()
+    internal val prefetchState = LazyLayoutPrefetchState(prefetchScheduler)
 
     private val numOfItemsToTeleport: Int get() = 100 * slotsPerLine
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
index 11c2a13..20d6ca5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
@@ -79,6 +79,15 @@
          * was precomposed already it will be disposed.
          */
         fun cancel()
+
+        /**
+         * Marks this prefetch request as urgent, which is a way to communicate that the requested
+         * item is expected to be needed during the next frame.
+         *
+         * For urgent requests we can proceed with doing the prefetch even if the available time
+         * in the frame is less than we spend on similar prefetch requests on average.
+         */
+        fun markAsUrgent()
     }
 
     private inner class NestedPrefetchScopeImpl : NestedPrefetchScope {
@@ -169,6 +178,7 @@
 @ExperimentalFoundationApi
 private object DummyHandle : PrefetchHandle {
     override fun cancel() {}
+    override fun markAsUrgent() {}
 }
 
 /**
@@ -212,6 +222,7 @@
         private val isComposed get() = precomposeHandle != null
         private var hasResolvedNestedPrefetches = false
         private var nestedPrefetchController: NestedPrefetchController? = null
+        private var isUrgent = false
 
         private val isValid
             get() = !isCanceled &&
@@ -225,13 +236,24 @@
             }
         }
 
+        override fun markAsUrgent() {
+            isUrgent = true
+        }
+
+        private fun PrefetchRequestScope.shouldExecute(average: Long): Boolean {
+            val available = availableTimeNanos()
+            // even for urgent request we only do the work if we have time available, as otherwise
+            // it is better to just return early to allow the next frame to start and do the work.
+            return (isUrgent && available > 0) || average < available
+        }
+
         override fun PrefetchRequestScope.execute(): Boolean {
             if (!isValid) {
                 return false
             }
 
             if (!isComposed) {
-                if (prefetchMetrics.averageCompositionTimeNanos < availableTimeNanos) {
+                if (shouldExecute(prefetchMetrics.averageCompositionTimeNanos)) {
                     prefetchMetrics.recordCompositionTiming {
                         trace("compose:lazy:prefetch:compose") {
                             performComposition()
@@ -242,27 +264,35 @@
                 }
             }
 
-            // Nested prefetch logic is best-effort: if nested LazyLayout children are
-            // added/removed/updated after we've resolved nested prefetch states here or resolved
-            // nestedPrefetchRequests below, those changes won't be taken into account.
-            if (!hasResolvedNestedPrefetches) {
-                if (availableTimeNanos > 0) {
-                    trace("compose:lazy:prefetch:resolve-nested") {
-                        nestedPrefetchController = resolveNestedPrefetchStates()
-                        hasResolvedNestedPrefetches = true
+            // if the request is urgent we better proceed with the measuring straight away instead
+            // of spending time trying to split the work more via nested prefetch. nested prefetch
+            // is always an estimation and it could potentially do work we will not need in the end,
+            // but the measuring will only do exactly the needed work (including composing nested
+            // lazy layouts)
+            if (!isUrgent) {
+                // Nested prefetch logic is best-effort: if nested LazyLayout children are
+                // added/removed/updated after we've resolved nested prefetch states here or resolved
+                // nestedPrefetchRequests below, those changes won't be taken into account.
+                if (!hasResolvedNestedPrefetches) {
+                    if (availableTimeNanos() > 0) {
+                        trace("compose:lazy:prefetch:resolve-nested") {
+                            nestedPrefetchController = resolveNestedPrefetchStates()
+                            hasResolvedNestedPrefetches = true
+                        }
+                    } else {
+                        return true
                     }
-                } else {
+                }
+
+                val hasMoreWork =
+                    nestedPrefetchController?.run { executeNestedPrefetches() } ?: false
+                if (hasMoreWork) {
                     return true
                 }
             }
 
-            val hasMoreWork = nestedPrefetchController?.run { executeNestedPrefetches() } ?: false
-            if (hasMoreWork) {
-                return true
-            }
-
             if (!isMeasured && constraints != null) {
-                if (prefetchMetrics.averageMeasureTimeNanos < availableTimeNanos) {
+                if (shouldExecute(prefetchMetrics.averageMeasureTimeNanos)) {
                     prefetchMetrics.recordMeasureTiming {
                         trace("compose:lazy:prefetch:measure") {
                             performMeasure(constraints)
@@ -349,7 +379,7 @@
                 trace("compose:lazy:prefetch:nested") {
                     while (stateIndex < states.size) {
                         if (requestsByState[stateIndex] == null) {
-                            if (availableTimeNanos <= 0) {
+                            if (availableTimeNanos() <= 0) {
                                 // When we have time again, we'll resolve nested requests for this
                                 // state
                                 return true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.kt
index d3497f8..131eb4f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchScheduler.kt
@@ -75,5 +75,5 @@
      * How much time is available to do prefetch work. Implementations of [PrefetchRequest] should
      * do their best to fit their work into this time without going over.
      */
-    val availableTimeNanos: Long
+    fun availableTimeNanos(): Long
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
index 9d9853c..3b8621c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
@@ -44,16 +44,17 @@
     }
 
     override fun calculateDistanceTo(targetIndex: Int): Float {
-        val visibleItem =
-            state.layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
+        val layoutInfo = state.layoutInfo
+        if (layoutInfo.visibleItemsInfo.isEmpty()) return 0f
+        val visibleItem = layoutInfo.visibleItemsInfo.fastFirstOrNull { it.index == targetIndex }
         return if (visibleItem == null) {
-            val averageMainAxisItemSize = visibleItemsAverageSize
+            val averageMainAxisItemSize = calculateVisibleItemsAverageSize(layoutInfo)
 
             val laneCount = state.laneCount
             val lineDiff = targetIndex / laneCount - firstVisibleItemIndex / laneCount
             averageMainAxisItemSize * lineDiff.toFloat() - firstVisibleItemScrollOffset
         } else {
-            if (state.layoutInfo.orientation == Orientation.Vertical) {
+            if (layoutInfo.orientation == Orientation.Vertical) {
                 visibleItem.offset.y
             } else {
                 visibleItem.offset.x
@@ -65,17 +66,15 @@
         state.scroll(block = block)
     }
 
-    private val visibleItemsAverageSize: Int
-        get() {
-            val layoutInfo = state.layoutInfo
-            val visibleItems = layoutInfo.visibleItemsInfo
-            val itemSizeSum = visibleItems.fastSumBy {
-                if (layoutInfo.orientation == Orientation.Vertical) {
-                    it.size.height
-                } else {
-                    it.size.width
-                }
+    private fun calculateVisibleItemsAverageSize(layoutInfo: LazyStaggeredGridLayoutInfo): Int {
+        val visibleItems = layoutInfo.visibleItemsInfo
+        val itemSizeSum = visibleItems.fastSumBy {
+            if (layoutInfo.orientation == Orientation.Vertical) {
+                it.size.height
+            } else {
+                it.size.width
             }
-            return itemSizeSum / visibleItems.size + layoutInfo.mainAxisItemSpacing
         }
+        return itemSizeSum / visibleItems.size + layoutInfo.mainAxisItemSpacing
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index 09cb286..3a15ecb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -34,6 +34,7 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.FullSpan
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
@@ -82,9 +83,10 @@
  * In most cases, it should be created via [rememberLazyStaggeredGridState].
  */
 @OptIn(ExperimentalFoundationApi::class)
-class LazyStaggeredGridState private constructor(
+class LazyStaggeredGridState internal constructor(
     initialFirstVisibleItems: IntArray,
     initialFirstVisibleOffsets: IntArray,
+    prefetchScheduler: PrefetchScheduler?
 ) : ScrollableState {
     /**
      * @param initialFirstVisibleItemIndex initial value for [firstVisibleItemIndex]
@@ -95,7 +97,8 @@
         initialFirstVisibleItemOffset: Int = 0
     ) : this(
         intArrayOf(initialFirstVisibleItemIndex),
-        intArrayOf(initialFirstVisibleItemOffset)
+        intArrayOf(initialFirstVisibleItemOffset),
+        null
     )
 
     /**
@@ -178,7 +181,7 @@
     internal var prefetchingEnabled: Boolean = true
 
     /** prefetch state used for precomputing items in the direction of scroll */
-    internal val prefetchState: LazyLayoutPrefetchState = LazyLayoutPrefetchState()
+    internal val prefetchState: LazyLayoutPrefetchState = LazyLayoutPrefetchState(prefetchScheduler)
 
     /** state controlling the scroll */
     private val scrollableState = ScrollableState { -onScroll(-it) }
@@ -584,7 +587,7 @@
                 )
             },
             restore = {
-                LazyStaggeredGridState(it[0], it[1])
+                LazyStaggeredGridState(it[0], it[1], null)
             }
         )
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index edf220c..a4310c5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
+import androidx.compose.foundation.lazy.layout.PrefetchScheduler
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
@@ -147,18 +148,26 @@
 
 /**
  * The state that can be used to control [VerticalPager] and [HorizontalPager]
- * @param currentPage The initial page to be displayed
- * @param currentPageOffsetFraction The offset of the initial page with respect to the start of
- * the layout.
  */
 @OptIn(ExperimentalFoundationApi::class)
 @Stable
-abstract class PagerState(
+abstract class PagerState internal constructor(
     currentPage: Int = 0,
-    @FloatRange(from = -0.5, to = 0.5) currentPageOffsetFraction: Float = 0f
+    @FloatRange(from = -0.5, to = 0.5) currentPageOffsetFraction: Float = 0f,
+    prefetchScheduler: PrefetchScheduler? = null
 ) : ScrollableState {
 
     /**
+     * @param currentPage The initial page to be displayed
+     * @param currentPageOffsetFraction The offset of the initial page with respect to the start of
+     * the layout.
+     */
+    constructor(
+        currentPage: Int = 0,
+        @FloatRange(from = -0.5, to = 0.5) currentPageOffsetFraction: Float = 0f
+    ) : this(currentPage, currentPageOffsetFraction, null)
+
+    /**
      * The total amount of pages present in this pager. The source of this data should be
      * observable.
      */
@@ -431,7 +440,7 @@
      */
     val currentPageOffsetFraction: Float get() = scrollPosition.currentPageOffsetFraction
 
-    internal val prefetchState = LazyLayoutPrefetchState()
+    internal val prefetchState = LazyLayoutPrefetchState(prefetchScheduler)
 
     internal val beyondBoundsInfo = LazyLayoutBeyondBoundsInfo()
 
@@ -716,21 +725,38 @@
             } else {
                 info.visiblePagesInfo.first().index - info.beyondViewportPageCount - PagesToPrefetch
             }
-            if (indexToPrefetch != this.indexToPrefetch &&
-                indexToPrefetch in 0 until pageCount
-            ) {
-                if (wasPrefetchingForward != isPrefetchingForward) {
-                    // the scrolling direction has been changed which means the last prefetched
-                    // is not going to be reached anytime soon so it is safer to dispose it.
-                    // if this item is already visible it is safe to call the method anyway
-                    // as it will be no-op
-                    currentPrefetchHandle?.cancel()
+            if (indexToPrefetch in 0 until pageCount) {
+                if (indexToPrefetch != this.indexToPrefetch) {
+                    if (wasPrefetchingForward != isPrefetchingForward) {
+                        // the scrolling direction has been changed which means the last prefetched
+                        // is not going to be reached anytime soon so it is safer to dispose it.
+                        // if this item is already visible it is safe to call the method anyway
+                        // as it will be no-op
+                        currentPrefetchHandle?.cancel()
+                    }
+                    this.wasPrefetchingForward = isPrefetchingForward
+                    this.indexToPrefetch = indexToPrefetch
+                    currentPrefetchHandle = prefetchState.schedulePrefetch(
+                        indexToPrefetch, premeasureConstraints
+                    )
                 }
-                this.wasPrefetchingForward = isPrefetchingForward
-                this.indexToPrefetch = indexToPrefetch
-                currentPrefetchHandle = prefetchState.schedulePrefetch(
-                    indexToPrefetch, premeasureConstraints
-                )
+                if (isPrefetchingForward) {
+                    val lastItem = info.visiblePagesInfo.last()
+                    val pageSize = info.pageSize + info.pageSpacing
+                    val distanceToReachNextItem =
+                        lastItem.offset + pageSize - info.viewportEndOffset
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToReachNextItem < delta) {
+                        currentPrefetchHandle?.markAsUrgent()
+                    }
+                } else {
+                    val firstItem = info.visiblePagesInfo.first()
+                    val distanceToReachNextItem = info.viewportStartOffset - firstItem.offset
+                    // if in the next frame we will get the same delta will we reach the item?
+                    if (distanceToReachNextItem < -delta) {
+                        currentPrefetchHandle?.markAsUrgent()
+                    }
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index c163320..00dd988 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -325,11 +325,9 @@
         .scrollable(
             state = scrollState,
             orientation = orientation,
-            // Disable scrolling when textField is disabled, there is no where to scroll, and
-            // another dragging gesture is taking place
-            enabled = enabled &&
-                scrollState.maxValue > 0 &&
-                textFieldSelectionState.draggingHandle == null,
+            // Disable scrolling when textField is disabled or another dragging gesture is taking
+            // place
+            enabled = enabled && textFieldSelectionState.draggingHandle == null,
             reverseDirection = ScrollableDefaults.reverseDirection(
                 layoutDirection = layoutDirection,
                 orientation = orientation,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 1338218..c8f3fc3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -434,9 +434,12 @@
                 TextFieldDelegate.draw(
                     canvas,
                     value,
+                    state.selectionPreviewHighlightRange,
+                    state.deletionPreviewHighlightRange,
                     offsetMapping,
                     layoutResult.value,
-                    state.selectionPaint
+                    state.highlightPaint,
+                    state.selectionBackgroundColor
                 )
             }
         }
@@ -614,7 +617,7 @@
         }
     }
 
-    val showCursor = enabled && !readOnly && windowInfo.isWindowFocused
+    val showCursor = enabled && !readOnly && windowInfo.isWindowFocused && !state.hasHighlight()
     val cursorModifier = Modifier.cursor(state, value, offsetMapping, cursorBrush, showCursor)
 
     DisposableEffect(manager) {
@@ -976,6 +979,8 @@
             // Text has been changed, enter the HandleState.None and hide the cursor handle.
             handleState = HandleState.None
         }
+        selectionPreviewHighlightRange = TextRange.Zero
+        deletionPreviewHighlightRange = TextRange.Zero
         onValueChangeOriginal(it)
         recomposeScope.invalidate()
     }
@@ -984,8 +989,16 @@
         keyboardActionRunner.runAction(imeAction)
     }
 
-    /** The paint used to draw highlight background for selected text. */
-    val selectionPaint: Paint = Paint()
+    /** The paint used to draw highlight backgrounds. */
+    val highlightPaint: Paint = Paint()
+    var selectionBackgroundColor = Color.Unspecified
+
+    /** Range of text to be highlighted to display handwriting gesture previews from the IME. */
+    var selectionPreviewHighlightRange: TextRange by mutableStateOf(TextRange.Zero)
+    var deletionPreviewHighlightRange: TextRange by mutableStateOf(TextRange.Zero)
+
+    fun hasHighlight() =
+        !selectionPreviewHighlightRange.collapsed || !deletionPreviewHighlightRange.collapsed
 
     fun update(
         untransformedText: AnnotatedString,
@@ -1000,7 +1013,7 @@
         selectionBackgroundColor: Color
     ) {
         this.>
-        this.selectionPaint.color = selectionBackgroundColor
+        this.selectionBackgroundColor = selectionBackgroundColor
         this.keyboardActionRunner.apply {
             this.keyboardActions = keyboardActions
             this.focusManager = focusManager
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
index b6567d5..eabeb8e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt
@@ -23,7 +23,9 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.isUnspecified
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.findRootCoordinates
 import androidx.compose.ui.text.AnnotatedString
@@ -112,28 +114,75 @@
          *
          * @param canvas The target canvas.
          * @param value The editor state
+         * @param selectionPreviewHighlightRange Range to be highlighted to preview a handwriting
+         *     selection gesture
+         * @param deletionPreviewHighlightRange Range to be highlighted to preview a handwriting
+         *     deletion gesture
          * @param offsetMapping The offset map
-         * @param selectionPaint The selection paint
+         * @param textLayoutResult The text layout result
+         * @param highlightPaint Paint used to draw highlight backgrounds
+         * @param selectionBackgroundColor The selection highlight background color
          */
         @JvmStatic
         internal fun draw(
             canvas: Canvas,
             value: TextFieldValue,
+            selectionPreviewHighlightRange: TextRange,
+            deletionPreviewHighlightRange: TextRange,
             offsetMapping: OffsetMapping,
             textLayoutResult: TextLayoutResult,
-            selectionPaint: Paint
+            highlightPaint: Paint,
+            selectionBackgroundColor: Color
         ) {
-            if (!value.selection.collapsed) {
-                val start = offsetMapping.originalToTransformed(value.selection.min)
-                val end = offsetMapping.originalToTransformed(value.selection.max)
-                if (start != end) {
-                    val selectionPath = textLayoutResult.getPathForRange(start, end)
-                    canvas.drawPath(selectionPath, selectionPaint)
-                }
+            if (!selectionPreviewHighlightRange.collapsed) {
+                highlightPaint.color = selectionBackgroundColor
+                drawHighlight(
+                    canvas,
+                    selectionPreviewHighlightRange,
+                    offsetMapping,
+                    textLayoutResult,
+                    highlightPaint
+                )
+            } else if (!deletionPreviewHighlightRange.collapsed) {
+                val textColor =
+                    textLayoutResult.layoutInput.style.color.takeUnless { it.isUnspecified }
+                        ?: Color.Black
+                highlightPaint.color = textColor.copy(alpha = textColor.alpha * 0.2f)
+                drawHighlight(
+                    canvas,
+                    deletionPreviewHighlightRange,
+                    offsetMapping,
+                    textLayoutResult,
+                    highlightPaint
+                )
+            } else if (!value.selection.collapsed) {
+                highlightPaint.color = selectionBackgroundColor
+                drawHighlight(
+                    canvas,
+                    value.selection,
+                    offsetMapping,
+                    textLayoutResult,
+                    highlightPaint
+                )
             }
             TextPainter.paint(canvas, textLayoutResult)
         }
 
+        private fun drawHighlight(
+            canvas: Canvas,
+            range: TextRange,
+            offsetMapping: OffsetMapping,
+            textLayoutResult: TextLayoutResult,
+            paint: Paint
+        ) {
+            val start = offsetMapping.originalToTransformed(range.min)
+            val end = offsetMapping.originalToTransformed(range.max)
+            if (start != end) {
+                val selectionPath = textLayoutResult.getPathForRange(start, end)
+                canvas.drawPath(selectionPath, paint)
+            }
+        }
+
         /**
          * Notify system that focused input area.
          *
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
index 7cf3de1..fe8166b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifier.kt
@@ -98,7 +98,14 @@
     // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
     // magnifier actually is). See
     // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
-    if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+    // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+    // created until it's requested for the first time. So the size will only be calculated after we
+    // return a specified offset from this function.
+    // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+    // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+    // of magnifier's width to hide by the following logic.
+    if (magnifierSize != IntSize.Zero &&
+        (dragX - centerX).absoluteValue > magnifierSize.width / 2) {
         return Offset.Unspecified
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 4fad5b6..465fc46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -58,6 +58,7 @@
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFold
 import androidx.compose.ui.util.fastForEach
@@ -277,7 +278,7 @@
 
         selectionRegistrar.>
             { isInTouchMode, selectableId ->
-                val (newSelection, newSubselection) = selectAll(
+                val (newSelection, newSubselection) = selectAllInSelectable(
                     selectableId = selectableId,
                     previousSelection = selection,
                 )
@@ -409,7 +410,7 @@
         return coordinates
     }
 
-    internal fun selectAll(
+    internal fun selectAllInSelectable(
         selectableId: Long,
         previousSelection: Selection?
     ): Pair<Selection?, LongObjectMap<Selection>> {
@@ -428,6 +429,70 @@
     }
 
     /**
+     * Returns whether the selection encompasses the entire container.
+     */
+    internal fun isEntireContainerSelected(): Boolean {
+        val selectables = selectionRegistrar.sort(requireContainerCoordinates())
+
+        // If there are no selectables, then an empty selection spans the entire container.
+        if (selectables.isEmpty()) return true
+
+        // Since some text exists, we must make sure that every selectable is fully selected.
+        return selectables.fastAll {
+            val text = it.getText()
+            if (text.isEmpty()) return@fastAll true // empty text is inherently fully selected
+
+            // If a non-empty selectable isn't included in the sub-selections,
+            // then some text in the container is not selected.
+            val subSelection = selectionRegistrar.subselections[it.selectableId]
+                ?: return@fastAll false
+
+            val selectionStart = subSelection.start.offset
+            val selectionEnd = subSelection.end.offset
+
+            // The selection could be reversed,
+            // so just verify that the difference between the two offsets matches the text length
+            (selectionStart - selectionEnd).absoluteValue == text.length
+        }
+    }
+
+    /**
+     * Creates and sets a selection spanning the entire container.
+     */
+    internal fun selectAll() {
+        val selectables = selectionRegistrar.sort(requireContainerCoordinates())
+        if (selectables.isEmpty()) return
+
+        var firstSubSelection: Selection? = null
+        var lastSubSelection: Selection? = null
+        val newSubSelections = mutableLongObjectMapOf<Selection>().apply {
+            selectables.fastForEach { selectable ->
+                val subSelection = selectable.getSelectAllSelection() ?: return@fastForEach
+                if (firstSubSelection == null) firstSubSelection = subSelection
+                lastSubSelection = subSelection
+                put(selectable.selectableId, subSelection)
+            }
+        }
+
+        if (newSubSelections.isEmpty()) return
+
+        // first/last sub selections are implied to be non-null from here on out
+        val newSelection = if (firstSubSelection === lastSubSelection) {
+            firstSubSelection
+        } else {
+            Selection(
+                start = firstSubSelection!!.start,
+                end = lastSubSelection!!.end,
+                handlesCrossed = false,
+            )
+        }
+
+        selectionRegistrar.subselections = newSubSelections
+        onSelectionChange(newSelection)
+        previousSelectionLayout = null
+    }
+
+    /**
      * Returns whether the start and end anchors are equal.
      *
      * It is possible that this returns true, but the selection is still empty because it has
@@ -521,9 +586,13 @@
         }
 
         val textToolbar = textToolbar ?: return
-        if (showToolbar && isInTouchMode && isNonEmptySelection()) {
+        if (showToolbar && isInTouchMode) {
             val rect = getContentRect() ?: return
-            textToolbar.showMenu(rect = rect, >
+            textToolbar.showMenu(
+                rect = rect,
+                 (isNonEmptySelection()) ::toolbarCopy else null,
+                 (isEntireContainerSelected()) null else ::selectAll,
+            )
         } else if (textToolbar.status == TextToolbarStatus.Shown) {
             textToolbar.hide()
         }
@@ -965,7 +1034,14 @@
     // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
     // magnifier actually is). See
     // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
-    if ((dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
+    // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+    // created until it's requested for the first time. So the size will only be calculated after we
+    // return a specified offset from this function.
+    // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+    // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+    // of magnifier's width to hide by the following logic.
+    if (magnifierSize != IntSize.Zero &&
+        (dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
         return Offset.Unspecified
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index bb5b445..a32cf68 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -576,6 +576,23 @@
         updateFloatingToolbar(show = false)
     }
 
+    internal fun setSelectionPreviewHighlight(range: TextRange) {
+        state?.selectionPreviewHighlightRange = range
+        state?.deletionPreviewHighlightRange = TextRange.Zero
+        if (!range.collapsed) exitSelectionMode()
+    }
+
+    internal fun setDeletionPreviewHighlight(range: TextRange) {
+        state?.deletionPreviewHighlightRange = range
+        state?.selectionPreviewHighlightRange = TextRange.Zero
+        if (!range.collapsed) exitSelectionMode()
+    }
+
+    internal fun clearPreviewHighlight() {
+        state?.deletionPreviewHighlightRange = TextRange.Zero
+        state?.selectionPreviewHighlightRange = TextRange.Zero
+    }
+
     /**
      * The method for copying text.
      *
@@ -1036,7 +1053,14 @@
     // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
     // magnifier actually is). See
     // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
-    if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+    // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+    // created until it's requested for the first time. So the size will only be calculated after we
+    // return a specified offset from this function.
+    // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+    // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+    // of magnifier's width to hide by the following logic.
+    if (magnifierSize != IntSize.Zero &&
+        (dragX - centerX).absoluteValue > magnifierSize.width / 2) {
         return Offset.Unspecified
     }
 
diff --git a/compose/integration-tests/demos/build.gradle b/compose/integration-tests/demos/build.gradle
index 1bfb611..5b3f0fa 100644
--- a/compose/integration-tests/demos/build.gradle
+++ b/compose/integration-tests/demos/build.gradle
@@ -31,6 +31,7 @@
     implementation(project(":compose:foundation:foundation-layout"))
     implementation(project(":compose:integration-tests:demos:common"))
     implementation(project(":compose:material:material"))
+    implementation(project(":compose:material:material-icons-extended"))
     implementation(project(":compose:material3:material3"))
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui"))
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
index 7dbf951..7acc778 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.Crossfade
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.demos.AccessibilityNodeInspectorButton
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -40,6 +41,7 @@
 import androidx.compose.material.LocalContentColor
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Api
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -101,8 +103,7 @@
                 >
             )
         },
-        modifier = Modifier
-            .nestedScroll(scrollBehavior.nestedScrollConnection)
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
     ) { innerPadding ->
         val modifier = Modifier
             // as scaffold currently doesn't consume - consume what's needed
@@ -246,6 +247,7 @@
             scrollBehavior = scrollBehavior,
             navigationIcon = navigationIcon,
             actions = {
+                AppBarIcons.AccessibilityNodeInspector()
                 AppBarIcons.Filter(>
                 AppBarIcons.Settings(>
             }
@@ -262,6 +264,15 @@
     }
 
     @Composable
+    fun AccessibilityNodeInspector() {
+        AccessibilityNodeInspectorButton {
+            IconButton( {
+                Icon(Icons.Filled.Api, contentDescription = null)
+            }
+        }
+    }
+
+    @Composable
     fun Filter(onClick: () -> Unit) {
         IconButton(modifier = Modifier.testTag(Tags.FilterButton),  {
             Icon(Icons.Filled.Search, null)
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index a905398..cccbd34 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -49,6 +49,19 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".StaticScrollingContentWithChromeInitialCompositionActivity"
+            android:label="C StaticScrollingWithChrome Init"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.STATIC_SCROLLING_CONTENT_WITH_CHROME_INITIAL_COMPOSITION_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.STATIC_SCROLLING_CONTENT_WITH_CHROME_FIRST_FRAME_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".TrivialStartupTracingActivity"
             android:label="C TrivialTracing"
             android:exported="true">
@@ -228,7 +241,8 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity
+        
+	<activity
             android:name=".VectorsListActivity"
             android:label="Compose vectors list"
             android:exported="true">
@@ -252,6 +266,14 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY" />
+            </intent-filter>
+	</activity>
+
+	<activity android:name=".PagerOfLazyGridActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.PAGER_LAZYGRID_ACTIVITY" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt
new file mode 100644
index 0000000..a18a1be
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import kotlinx.coroutines.launch
+
+class PagerOfLazyGridActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val pageCount = intent.getIntExtra(PageCount, 100)
+        val gridItemCount = intent.getIntExtra(GridItemCount, 100)
+
+        setContent {
+            MaterialTheme {
+                HorizontalPagerOfLazyGrid(pageCount, gridItemCount)
+            }
+        }
+
+        launchIdlenessTracking()
+    }
+
+    companion object {
+        const val PageCount = "PAGE_COUNT"
+        const val GridItemCount = "GRID_ITEM_COUNT"
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun HorizontalPagerOfLazyGrid(pages: Int = 100, gridItems: Int = 100) {
+    val pagerState: PagerState = rememberPagerState(initialPage = 1) { pages }
+    val coroutineScope = rememberCoroutineScope()
+
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .background(MaterialTheme.colors.background)
+    ) {
+        Button(>
+            coroutineScope.launch {
+                pagerState.animateScrollToPage(pagerState.currentPage + 1)
+            }
+        }) {
+            Text("Next")
+        }
+
+        HorizontalPager(
+            state = pagerState,
+            modifier = Modifier.semantics { contentDescription = "Pager" }
+        ) { page: Int ->
+            Grid(gridItems, page)
+        }
+    }
+}
+
+@Composable
+private fun Grid(itemCount: Int, pageNum: Int) {
+    val text = remember(pageNum) { "Hello + $pageNum" }
+    LazyVerticalGrid(
+        modifier = Modifier.fillMaxSize(),
+        columns = GridCells.Fixed(3),
+    ) {
+        items(itemCount, contentType = { "cell" }) { _ ->
+            Button( {
+                Text(text = text)
+            }
+        }
+    }
+}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/StaticScrollingContentWithChromeInitialCompositionActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/StaticScrollingContentWithChromeInitialCompositionActivity.kt
new file mode 100644
index 0000000..87810a2
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/StaticScrollingContentWithChromeInitialCompositionActivity.kt
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.BottomNavigation
+import androidx.compose.material.BottomNavigationItem
+import androidx.compose.material.Button
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Place
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.trace
+
+class StaticScrollingContentWithChromeInitialCompositionActivity : ComponentActivity() {
+
+    private val onlyPerformComposition: Boolean
+        get() = intent.action == "androidx.compose.integration.macrobenchmark.target" +
+            ".STATIC_SCROLLING_CONTENT_WITH_CHROME_INITIAL_COMPOSITION_ACTIVITY"
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            if (onlyPerformComposition) {
+                ComposeOnlyLayout {
+                    StaticScrollingContentWithChrome(
+                        modifier = Modifier
+                            .onPlaced { _ ->
+                                throw RuntimeException(
+                                    "Content was placed, but should only be composed"
+                                )
+                            }
+                            .drawWithContent {
+                                throw RuntimeException(
+                                    "Content was drawn, but should only be composed"
+                                )
+                            }
+                    )
+                }
+            } else {
+                StaticScrollingContentWithChrome()
+            }
+        }
+    }
+}
+
+/**
+ * A layout that will compose all of the [content], but will not place
+ * (and therefore not layout or draw) any of its children.
+ *
+ * This is useful for this benchmark as we care about the composition time. A major limitation
+ * of this approach is that any content in a SubcomposeLayout will not be composed
+ * and will not contribute to the overall measured time of this test.
+ */
+@Composable
+private fun ComposeOnlyLayout(
+    content: @Composable () -> Unit
+) {
+    Layout(content) { _, _ -> layout(0, 0) {} }
+}
+
+@Preview
+@Composable
+private fun StaticScrollingContentWithChrome(
+    modifier: Modifier = Modifier
+) = trace(sectionName = "StaticScrollingContentWithChrome") {
+    Column(modifier) {
+        TopBar()
+        ScrollingContent(modifier = Modifier.weight(1f))
+        BottomBar()
+    }
+}
+
+@Composable
+private fun TopBar(modifier: Modifier = Modifier) {
+    TopAppBar(
+        modifier = modifier,
+        title = {
+            Column {
+                Text(
+                    "Initial Composition Macrobench",
+                    style = MaterialTheme.typography.subtitle1,
+                    maxLines = 1
+                )
+                Text(
+                    "Static Scrolling Content w/ Chrome",
+                    style = MaterialTheme.typography.caption,
+                    maxLines = 1
+                )
+            }
+        },
+        navigationIcon = {
+            Button( {
+                Icon(Icons.Default.Close, "Dismiss")
+            }
+        },
+        actions = {
+            Button( {
+                Icon(Icons.Default.MoreVert, "Actions")
+            }
+        }
+    )
+}
+
+@Composable
+private fun BottomBar(modifier: Modifier = Modifier) {
+    BottomNavigation(modifier = modifier) {
+        BottomNavigationItem(
+            selected = true,
+            >
+            icon = { Icon(Icons.Default.Home, "Home") }
+        )
+        BottomNavigationItem(
+            selected = false,
+            >
+            icon = { Icon(Icons.Default.Add, "Add") }
+        )
+    }
+}
+
+@Composable
+private fun ScrollingContent(modifier: Modifier = Modifier) {
+    Column(
+        modifier
+            .fillMaxSize()
+            .verticalScroll(rememberScrollState())
+            .padding(vertical = 16.dp)
+    ) {
+        Item(
+            color = Color.DarkGray,
+            icon = Icons.Filled.Info,
+            modifier = Modifier
+                .padding(horizontal = 16.dp)
+                .aspectRatio(16f / 9f)
+                .fillMaxWidth()
+                .clip(RoundedCornerShape(16.dp))
+        )
+
+        repeat(5) { iteration ->
+            CardGroup(
+                title = "Group ${4 * iteration}",
+                groupIcon = Icons.Filled.Person,
+                groupColor = Color(0xFF1967D2)
+            )
+
+            CardGroup(
+                title = "Group ${4 * iteration + 1}",
+                groupIcon = Icons.Filled.Favorite,
+                groupColor = Color(0xFFC5221F)
+            )
+
+            CardGroup(
+                title = "Group ${4 * iteration + 2}",
+                groupIcon = Icons.Filled.Star,
+                groupColor = Color(0xFFF29900)
+            )
+
+            CardGroup(
+                title = "Group ${4 * iteration + 3}",
+                groupIcon = Icons.Filled.Place,
+                groupColor = Color(0xFF188038)
+            )
+        }
+    }
+}
+
+@Composable
+private fun CardGroup(
+    title: String,
+    groupIcon: ImageVector,
+    groupColor: Color,
+    modifier: Modifier = Modifier,
+    count: Int = 10
+) {
+    Column(
+        modifier = modifier
+    ) {
+        Text(
+            title,
+            style = MaterialTheme.typography.h6,
+            modifier = Modifier.padding(16.dp)
+        )
+
+        Row(
+            modifier = Modifier
+                .horizontalScroll(rememberScrollState())
+                .padding(horizontal = 12.dp)
+        ) {
+            repeat(count) {
+                Item(
+                    color = groupColor,
+                    icon = groupIcon,
+                    modifier = Modifier
+                        .padding(horizontal = 4.dp)
+                        .size(64.dp)
+                        .clip(RoundedCornerShape(4.dp))
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Item(
+    color: Color,
+    icon: ImageVector,
+    modifier: Modifier = Modifier
+) {
+    Box(
+        modifier = modifier.background(color),
+        contentAlignment = Alignment.Center
+    ) {
+        Icon(icon, null, tint = Color.White)
+    }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
index 003b307..c070d43 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
@@ -19,6 +19,8 @@
 import android.content.Intent
 import android.graphics.Point
 import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
 import androidx.benchmark.macro.FrameTimingMetric
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.platform.app.InstrumentationRegistry
@@ -41,11 +43,12 @@
         device = UiDevice.getInstance(instrumentation)
     }
 
+    @OptIn(ExperimentalMetricApi::class)
     @Test
     fun start() {
         benchmarkRule.measureRepeated(
             packageName = PACKAGE_NAME,
-            metrics = listOf(FrameTimingMetric()),
+            metrics = listOf(FrameTimingMetric(), FrameTimingGfxInfoMetric()),
             compilationMode = CompilationMode.Full(),
             iterations = 8,
             setupBlock = {
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerOfLazyGridBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerOfLazyGridBenchmark.kt
new file mode 100644
index 0000000..91a4b14
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerOfLazyGridBenchmark.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import androidx.testutils.createCompilationParams
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class PagerOfLazyGridBenchmark(
+    private val compilationMode: CompilationMode
+) {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    private lateinit var device: UiDevice
+
+    @Before
+    fun setUp() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        device = UiDevice.getInstance(instrumentation)
+    }
+
+    @Test
+    fun scroll() {
+        benchmarkRule.measureRepeated(
+            packageName = PackageName,
+            metrics = listOf(FrameTimingMetric()),
+            compilationMode = compilationMode,
+            startupMode = StartupMode.WARM,
+            iterations = 10,
+            setupBlock = {
+                val intent = Intent()
+                intent.action = Action
+                startActivityAndWait(intent)
+            }
+        ) {
+            val nextButton = device.findObject(By.text(NextDescription))
+            repeat(3) {
+                nextButton.click()
+                device.wait(Until.findObject(By.desc(ComposeIdle)), 3000)
+            }
+        }
+    }
+
+    companion object {
+        private const val PackageName = "androidx.compose.integration.macrobenchmark.target"
+        private const val Action =
+            "androidx.compose.integration.macrobenchmark.target.PAGER_LAZYGRID_ACTIVITY"
+        private const val ComposeIdle = "COMPOSE-IDLE"
+        private const val NextDescription = "Next"
+
+        @Parameterized.Parameters(name = "compilation={0}")
+        @JvmStatic
+        fun parameters() = createCompilationParams()
+    }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/StaticScrollingContentWithChromeInitialCompositionBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/StaticScrollingContentWithChromeInitialCompositionBenchmark.kt
new file mode 100644
index 0000000..b573368
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/StaticScrollingContentWithChromeInitialCompositionBenchmark.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class StaticScrollingContentWithChromeInitialCompositionBenchmark(
+    private val startupMode: StartupMode,
+    private val compilationMode: CompilationMode
+) {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    @Test
+    fun initialComposition() = benchmarkRule.measureStartup(
+        compilationMode = compilationMode,
+        startupMode = startupMode,
+        packageName = "androidx.compose.integration.macrobenchmark.target"
+    ) {
+        action = "androidx.compose.integration.macrobenchmark.target" +
+            ".STATIC_SCROLLING_CONTENT_WITH_CHROME_INITIAL_COMPOSITION_ACTIVITY"
+    }
+
+    @Test
+    fun firstFrame() = benchmarkRule.measureStartup(
+        compilationMode = compilationMode,
+        startupMode = startupMode,
+        packageName = "androidx.compose.integration.macrobenchmark.target"
+    ) {
+        action = "androidx.compose.integration.macrobenchmark.target" +
+            ".STATIC_SCROLLING_CONTENT_WITH_CHROME_FIRST_FRAME_ACTIVITY"
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "startup={0},compilation={1}")
+        @JvmStatic
+        fun parameters() = createStartupCompilationParams()
+    }
+}
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 7c87844..69623fb 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -646,12 +646,10 @@
   }
 
   @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class RippleConfiguration {
-    ctor public RippleConfiguration(optional boolean isEnabled, optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
+    ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
     method public long getColor();
     method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
-    method public boolean isEnabled();
     property public final long color;
-    property public final boolean isEnabled;
     property public final androidx.compose.material.ripple.RippleAlpha? rippleAlpha;
   }
 
@@ -662,11 +660,11 @@
   }
 
   public final class RippleKt {
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration> getLocalRippleConfiguration();
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
-    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration> LocalRippleConfiguration;
+    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
     property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
   }
 
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 7c87844..69623fb 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -646,12 +646,10 @@
   }
 
   @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class RippleConfiguration {
-    ctor public RippleConfiguration(optional boolean isEnabled, optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
+    ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
     method public long getColor();
     method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
-    method public boolean isEnabled();
     property public final long color;
-    property public final boolean isEnabled;
     property public final androidx.compose.material.ripple.RippleAlpha? rippleAlpha;
   }
 
@@ -662,11 +660,11 @@
   }
 
   public final class RippleKt {
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration> getLocalRippleConfiguration();
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> getLocalRippleConfiguration();
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
-    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration> LocalRippleConfiguration;
+    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material.RippleConfiguration?> LocalRippleConfiguration;
     property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
   }
 
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
index 15c1d55..c52867d 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/RippleTest.kt
@@ -1015,10 +1015,6 @@
     fun rippleConfiguration_disabled_dragged() {
         val interactionSource = MutableInteractionSource()
 
-        val rippleConfiguration = RippleConfiguration(
-            isEnabled = false
-        )
-
         var scope: CoroutineScope? = null
 
         rule.setContent {
@@ -1026,7 +1022,7 @@
             MaterialTheme {
                 Surface {
                     CompositionLocalProvider(
-                        LocalRippleConfiguration provides rippleConfiguration
+                        LocalRippleConfiguration provides null
                     ) {
                         Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                             RippleBoxWithBackground(
@@ -1072,7 +1068,7 @@
 
         val contentColor = Color.Black
 
-        var rippleConfiguration by mutableStateOf(RippleConfiguration())
+        var rippleConfiguration: RippleConfiguration? by mutableStateOf(RippleConfiguration())
 
         var scope: CoroutineScope? = null
 
@@ -1119,7 +1115,6 @@
         }
 
         val newConfiguration = RippleConfiguration(
-            isEnabled = true,
             color = Color.Red,
             rippleAlpha = RippleAlpha(0.5f, 0.5f, 0.5f, 0.5f)
         )
@@ -1147,7 +1142,7 @@
         }
 
         rule.runOnUiThread {
-            rippleConfiguration = RippleConfiguration(isEnabled = false)
+            rippleConfiguration = null
         }
 
         with(rule.onNodeWithTag(Tag)) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
index 50cca67..7b371e3 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Ripple.kt
@@ -200,7 +200,8 @@
 /**
  * CompositionLocal used for providing [RippleConfiguration] down the tree. This acts as a
  * tree-local 'override' for ripples used inside components that you cannot directly control, such
- * as to change the color of a specific component's ripple, or disable it entirely.
+ * as to change the color of a specific component's ripple, or disable it entirely by providing
+ * `null`.
  *
  * In most cases you should rely on the default theme behavior for consistency with other components
  * - this exists as an escape hatch for individual components and is not intended to be used for
@@ -211,15 +212,15 @@
 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
 @get:ExperimentalMaterialApi
 @ExperimentalMaterialApi
-val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration> =
+val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration?> =
     compositionLocalOf { RippleConfiguration() }
 
 /**
  * Configuration for [ripple] appearance, provided using [LocalRippleConfiguration]. In most cases
  * the default values should be used, for custom design system use cases you should instead
- * build your own custom ripple using [createRippleModifierNode].
+ * build your own custom ripple using [createRippleModifierNode]. To disable the ripple, provide
+ * `null` using [LocalRippleConfiguration].
  *
- * @param isEnabled whether the ripple is enabled. If false, no ripple will be rendered
  * @param color the color override for the ripple. If [Color.Unspecified], then the default color
  * from the theme will be used instead. Note that if the ripple has a color explicitly set with
  * the parameter on [ripple], that will always be used instead of this value.
@@ -229,7 +230,6 @@
 @Immutable
 @ExperimentalMaterialApi
 class RippleConfiguration(
-    val isEnabled: Boolean = true,
     val color: Color = Color.Unspecified,
     val rippleAlpha: RippleAlpha? = null
 ) {
@@ -237,7 +237,6 @@
         if (this === other) return true
         if (other !is RippleConfiguration) return false
 
-        if (isEnabled != other.isEnabled) return false
         if (color != other.color) return false
         if (rippleAlpha != other.rippleAlpha) return false
 
@@ -245,14 +244,13 @@
     }
 
     override fun hashCode(): Int {
-        var result = isEnabled.hashCode()
-        result = 31 * result + color.hashCode()
+        var result = color.hashCode()
         result = 31 * result + (rippleAlpha?.hashCode() ?: 0)
         return result
     }
 
     override fun toString(): String {
-        return "RippleConfiguration(enabled=$isEnabled, color=$color, rippleAlpha=$rippleAlpha)"
+        return "RippleConfiguration(color=$color, rippleAlpha=$rippleAlpha)"
     }
 }
 
@@ -353,13 +351,14 @@
     }
 
     /**
-     * Handles changes to [RippleConfiguration.isEnabled]. Changes to [RippleConfiguration.color] and
-     * [RippleConfiguration.rippleAlpha] are handled as part of the ripple definition.
+     * Handles [LocalRippleConfiguration] changing between null / non-null. Changes to
+     * [RippleConfiguration.color] and [RippleConfiguration.rippleAlpha] are handled as part of
+     * the ripple definition.
      */
     private fun updateConfiguration() {
         observeReads {
             val configuration = currentValueOf(LocalRippleConfiguration)
-            if (!configuration.isEnabled) {
+            if (configuration == null) {
                 removeRipple()
             } else {
                 if (rippleNode == null) attachNewRipple()
@@ -373,8 +372,10 @@
             if (userDefinedColor.isSpecified) {
                 userDefinedColor
             } else {
+                // If this is null, the ripple will be removed, so this should always be non-null in
+                // normal use
                 val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
-                if (rippleConfiguration.color.isSpecified) {
+                if (rippleConfiguration?.color?.isSpecified == true) {
                     rippleConfiguration.color
                 } else {
                     RippleDefaults.rippleColor(
@@ -385,8 +386,10 @@
             }
         }
         val calculateRippleAlpha = {
+            // If this is null, the ripple will be removed, so this should always be non-null in
+            // normal use
             val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
-            rippleConfiguration.rippleAlpha ?: RippleDefaults.rippleAlpha(
+            rippleConfiguration?.rippleAlpha ?: RippleDefaults.rippleAlpha(
                 contentColor = currentValueOf(LocalContentColor),
                 lightTheme = currentValueOf(LocalColors).isLight
             )
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index cfed154..83a3415 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1255,12 +1255,10 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class RippleConfiguration {
-    ctor public RippleConfiguration(optional boolean isEnabled, optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
+    ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
     method public long getColor();
     method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
-    method public boolean isEnabled();
     property public final long color;
-    property public final boolean isEnabled;
     property public final androidx.compose.material.ripple.RippleAlpha? rippleAlpha;
   }
 
@@ -1271,11 +1269,11 @@
   }
 
   public final class RippleKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration> getLocalRippleConfiguration();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration> LocalRippleConfiguration;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index cfed154..83a3415 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1255,12 +1255,10 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class RippleConfiguration {
-    ctor public RippleConfiguration(optional boolean isEnabled, optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
+    ctor public RippleConfiguration(optional long color, optional androidx.compose.material.ripple.RippleAlpha? rippleAlpha);
     method public long getColor();
     method public androidx.compose.material.ripple.RippleAlpha? getRippleAlpha();
-    method public boolean isEnabled();
     property public final long color;
-    property public final boolean isEnabled;
     property public final androidx.compose.material.ripple.RippleAlpha? rippleAlpha;
   }
 
@@ -1271,11 +1269,11 @@
   }
 
   public final class RippleKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration> getLocalRippleConfiguration();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> getLocalRippleConfiguration();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
     method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration> LocalRippleConfiguration;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.RippleConfiguration?> LocalRippleConfiguration;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
   }
 
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
index a802cae..80cc29d 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RippleTest.kt
@@ -503,10 +503,6 @@
     fun rippleConfiguration_disabled_dragged() {
         val interactionSource = MutableInteractionSource()
 
-        val rippleConfiguration = RippleConfiguration(
-            isEnabled = false
-        )
-
         var scope: CoroutineScope? = null
 
         rule.setContent {
@@ -514,7 +510,7 @@
             MaterialTheme {
                 Surface {
                     CompositionLocalProvider(
-                        LocalRippleConfiguration provides rippleConfiguration
+                        LocalRippleConfiguration provides null
                     ) {
                         Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                             RippleBoxWithBackground(
@@ -560,7 +556,7 @@
 
         val contentColor = Color.Black
 
-        var rippleConfiguration by mutableStateOf(RippleConfiguration())
+        var rippleConfiguration: RippleConfiguration? by mutableStateOf(RippleConfiguration())
 
         var scope: CoroutineScope? = null
 
@@ -607,7 +603,6 @@
         }
 
         val newConfiguration = RippleConfiguration(
-            isEnabled = true,
             color = Color.Red,
             rippleAlpha = RippleAlpha(0.5f, 0.5f, 0.5f, 0.5f)
         )
@@ -635,7 +630,7 @@
         }
 
         rule.runOnUiThread {
-            rippleConfiguration = RippleConfiguration(isEnabled = false)
+            rippleConfiguration = null
         }
 
         with(rule.onNodeWithTag(Tag)) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
index 322b33f..485b51c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Ripple.kt
@@ -165,7 +165,8 @@
 /**
  * CompositionLocal used for providing [RippleConfiguration] down the tree. This acts as a
  * tree-local 'override' for ripples used inside components that you cannot directly control, such
- * as to change the color of a specific component's ripple, or disable it entirely.
+ * as to change the color of a specific component's ripple, or disable it entirely by providing
+ * `null`.
  *
  * In most cases you should rely on the default theme behavior for consistency with other components
  * - this exists as an escape hatch for individual components and is not intended to be used for
@@ -176,15 +177,15 @@
 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
 @get:ExperimentalMaterial3Api
 @ExperimentalMaterial3Api
-val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration> =
+val LocalRippleConfiguration: ProvidableCompositionLocal<RippleConfiguration?> =
     compositionLocalOf { RippleConfiguration() }
 
 /**
  * Configuration for [ripple] appearance, provided using [LocalRippleConfiguration]. In most cases
  * the default values should be used, for custom design system use cases you should instead
- * build your own custom ripple using [createRippleModifierNode].
+ * build your own custom ripple using [createRippleModifierNode]. To disable the ripple, provide
+ * `null` using [LocalRippleConfiguration].
  *
- * @param isEnabled whether the ripple is enabled. If false, no ripple will be rendered
  * @param color the color override for the ripple. If [Color.Unspecified], then the default color
  * from the theme will be used instead. Note that if the ripple has a color explicitly set with
  * the parameter on [ripple], that will always be used instead of this value.
@@ -194,7 +195,6 @@
 @Immutable
 @ExperimentalMaterial3Api
 class RippleConfiguration(
-    val isEnabled: Boolean = true,
     val color: Color = Color.Unspecified,
     val rippleAlpha: RippleAlpha? = null
 ) {
@@ -202,7 +202,6 @@
         if (this === other) return true
         if (other !is RippleConfiguration) return false
 
-        if (isEnabled != other.isEnabled) return false
         if (color != other.color) return false
         if (rippleAlpha != other.rippleAlpha) return false
 
@@ -210,14 +209,13 @@
     }
 
     override fun hashCode(): Int {
-        var result = isEnabled.hashCode()
-        result = 31 * result + color.hashCode()
+        var result = color.hashCode()
         result = 31 * result + (rippleAlpha?.hashCode() ?: 0)
         return result
     }
 
     override fun toString(): String {
-        return "RippleConfiguration(enabled=$isEnabled, color=$color, rippleAlpha=$rippleAlpha)"
+        return "RippleConfiguration(color=$color, rippleAlpha=$rippleAlpha)"
     }
 }
 
@@ -311,13 +309,14 @@
     }
 
     /**
-     * Handles changes to [RippleConfiguration.isEnabled]. Changes to [RippleConfiguration.color] and
-     * [RippleConfiguration.rippleAlpha] are handled as part of the ripple definition.
+     * Handles [LocalRippleConfiguration] changing between null / non-null. Changes to
+     * [RippleConfiguration.color] and [RippleConfiguration.rippleAlpha] are handled as part of
+     * the ripple definition.
      */
     private fun updateConfiguration() {
         observeReads {
             val configuration = currentValueOf(LocalRippleConfiguration)
-            if (!configuration.isEnabled) {
+            if (configuration == null) {
                 removeRipple()
             } else {
                 if (rippleNode == null) attachNewRipple()
@@ -331,8 +330,10 @@
             if (userDefinedColor.isSpecified) {
                 userDefinedColor
             } else {
+                // If this is null, the ripple will be removed, so this should always be non-null in
+                // normal use
                 val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
-                if (rippleConfiguration.color.isSpecified) {
+                if (rippleConfiguration?.color?.isSpecified == true) {
                     rippleConfiguration.color
                 } else {
                     currentValueOf(LocalContentColor)
@@ -341,8 +342,10 @@
         }
 
         val calculateRippleAlpha = {
+            // If this is null, the ripple will be removed, so this should always be non-null in
+            // normal use
             val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
-            rippleConfiguration.rippleAlpha ?: RippleDefaults.RippleAlpha
+            rippleConfiguration?.rippleAlpha ?: RippleDefaults.RippleAlpha
         }
 
         rippleNode = delegate(createRippleModifierNode(
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SlotTableIntegrationBenchmark.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SlotTableIntegrationBenchmark.kt
new file mode 100644
index 0000000..9b61a35
--- /dev/null
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SlotTableIntegrationBenchmark.kt
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlin.random.Random
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTestApi::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SlotTableIntegrationBenchmark : ComposeBenchmarkBase() {
+
+    @UiThreadTest
+    @Test
+    fun create() = runBlockingTestWithFrameClock {
+        measureCompose {
+            Column(
+                modifier = Modifier.size(width = 20.dp, height = 300.dp)
+            ) {
+                repeat(100) {
+                    key(it) {
+                        Pixel(color = Color.Blue)
+                    }
+                }
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun removeManyGroups() = runBlockingTestWithFrameClock {
+        var includeGroups by mutableStateOf(true)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    if (includeGroups) {
+                        repeat(100) {
+                            key(it) {
+                                Pixel(color = Color.Blue)
+                            }
+                        }
+                    }
+                }
+            }
+            update { includeGroups = false }
+            reset { includeGroups = true }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun removeAlternatingGroups() = runBlockingTestWithFrameClock {
+        var insertAlternatingGroups by mutableStateOf(true)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    repeat(100) { index ->
+                        if (index % 2 == 0 || insertAlternatingGroups) {
+                            key(index) {
+                                Pixel(color = Color.Blue)
+                            }
+                        }
+                    }
+                }
+            }
+            update { insertAlternatingGroups = false }
+            reset { insertAlternatingGroups = true }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun removeManyReplaceGroups() = runBlockingTestWithFrameClock {
+        var insertAlternatingGroups by mutableStateOf(true)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    repeat(100) { index ->
+                        if (index % 2 == 0 || insertAlternatingGroups) {
+                            Pixel(color = Color(
+                                red = 0,
+                                green = 2 * index,
+                                blue = 0
+                            ))
+                        }
+                    }
+                }
+            }
+            update { insertAlternatingGroups = false }
+            reset { insertAlternatingGroups = true }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun insertManyGroups() = runBlockingTestWithFrameClock {
+        var includeGroups by mutableStateOf(false)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    if (includeGroups) {
+                        repeat(100) {
+                            key(it) {
+                                Pixel(color = Color.Blue)
+                            }
+                        }
+                    }
+                }
+            }
+            update { includeGroups = true }
+            reset { includeGroups = false }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun insertAlternatingGroups() = runBlockingTestWithFrameClock {
+        var insertAlternatingGroups by mutableStateOf(false)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    repeat(100) { index ->
+                        if (index % 2 == 0 || insertAlternatingGroups) {
+                            key(index) {
+                                Pixel(color = Color.Blue)
+                            }
+                        }
+                    }
+                }
+            }
+            update { insertAlternatingGroups = true }
+            reset { insertAlternatingGroups = false }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun insertManyReplaceGroups() = runBlockingTestWithFrameClock {
+        var insertAlternatingGroups by mutableStateOf(false)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    repeat(100) { index ->
+                        if (index % 2 == 0 || insertAlternatingGroups) {
+                            Pixel(color = Color(
+                                red = 0,
+                                green = 2 * index,
+                                blue = 0
+                            ))
+                        }
+                    }
+                }
+            }
+            update { insertAlternatingGroups = true }
+            reset { insertAlternatingGroups = false }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun updateManyNestedGroups() = runBlockingTestWithFrameClock {
+        var seed by mutableIntStateOf(1337)
+        measureRecomposeSuspending {
+            compose {
+                val random = remember(seed) { Random(seed) }
+                MatryoshkaLayout(
+                    depth = 100,
+                    content = {
+                        MinimalBox {
+                            Pixel(color = Color(random.nextInt()))
+                            Pixel(color = Color.Red)
+                            Pixel(color = Color.Green)
+                            Pixel(color = Color.Blue)
+                        }
+                        MinimalBox {
+                            NonRenderingText("abcdef")
+                        }
+                        NonRenderingText(
+                            text = random.nextString(),
+                            textColor = Color(random.nextInt()),
+                            textSize = random.nextInt(6, 32).dp,
+                            ellipsize = random.nextBoolean(),
+                            minLines = random.nextInt(),
+                            maxLines = random.nextInt(),
+                        )
+                    }
+                )
+            }
+            update { seed++ }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun updateDisjointGroups() = runBlockingTestWithFrameClock {
+        var seed by mutableIntStateOf(1337)
+        measureRecomposeSuspending {
+            compose {
+                MinimalBox {
+                    repeat(10) { container ->
+                        MinimalBox {
+                            MatryoshkaLayout(
+                                depth = 100,
+                                content = { depth ->
+                                    if (depth > 50) {
+                                        val random = Random(seed * container + depth)
+                                        NonRenderingText(
+                                            text = random.nextString(),
+                                            textColor = Color(random.nextInt()),
+                                            textSize = random.nextInt(6, 32).dp,
+                                            ellipsize = random.nextBoolean(),
+                                            minLines = random.nextInt(),
+                                            maxLines = random.nextInt(),
+                                        )
+                                    } else {
+                                        NonRenderingText("foo")
+                                    }
+                                }
+                            )
+                        }
+                    }
+                }
+            }
+            update { seed++ }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun updateDeepCompositionLocalHierarchy() = runBlockingTestWithFrameClock {
+        val PixelColorLocal = compositionLocalOf { Color.Unspecified }
+        var seed by mutableIntStateOf(1337)
+        measureRecomposeSuspending {
+            compose {
+                val random = remember(seed) { Random(seed) }
+                Pixel(PixelColorLocal.current)
+                CompositionLocalProvider(
+                    PixelColorLocal provides Color(random.nextInt())
+                ) {
+                    Pixel(PixelColorLocal.current)
+                    CompositionLocalProvider(
+                        PixelColorLocal provides Color(random.nextInt())
+                    ) {
+                        Pixel(PixelColorLocal.current)
+                        CompositionLocalProvider(
+                            PixelColorLocal provides Color(random.nextInt())
+                        ) {
+                            Pixel(PixelColorLocal.current)
+                            CompositionLocalProvider(
+                                PixelColorLocal provides Color(random.nextInt())
+                            ) {
+                                Pixel(PixelColorLocal.current)
+                                CompositionLocalProvider(
+                                    PixelColorLocal provides Color(random.nextInt())
+                                ) {
+                                    Pixel(PixelColorLocal.current)
+                                    CompositionLocalProvider(
+                                        PixelColorLocal provides Color(random.nextInt())
+                                    ) {
+                                        Pixel(PixelColorLocal.current)
+                                        CompositionLocalProvider(
+                                            PixelColorLocal provides Color(random.nextInt())
+                                        ) {
+                                            Pixel(PixelColorLocal.current)
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            update { seed++ }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun reverseGroups() = runBlockingTestWithFrameClock {
+        val originalItems = (1..100).toList()
+        var keys by mutableStateOf(originalItems)
+        measureRecomposeSuspending {
+            compose {
+                Column(
+                    modifier = Modifier.size(width = 20.dp, height = 300.dp)
+                ) {
+                    keys.forEach {
+                        key(it) {
+                            Pixel(color = Color.Blue)
+                        }
+                    }
+                }
+            }
+            update { keys = keys.reversed() }
+            reset { keys = originalItems }
+        }
+    }
+}
+
+@Composable
+private fun Pixel(color: Color) {
+    Layout(
+        modifier = Modifier.background(color)
+    ) { _, _ ->
+        layout(1, 1) {}
+    }
+}
+
+@Composable
+private fun NonRenderingText(
+    text: String,
+    textColor: Color = Color.Unspecified,
+    textSize: Dp = Dp.Unspecified,
+    ellipsize: Boolean = false,
+    minLines: Int = 1,
+    maxLines: Int = Int.MAX_VALUE
+) {
+    use(text)
+    use(textColor.value.toInt())
+    use(textSize.value)
+    use(ellipsize)
+    use(minLines)
+    use(maxLines)
+    Layout { _, _ ->
+        layout(1, 1) {}
+    }
+}
+
+@Composable
+private fun MinimalBox(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit
+) {
+    Layout(content, modifier, MinimalBoxMeasurePolicy)
+}
+
+@Composable
+private fun MatryoshkaLayout(
+    depth: Int,
+    content: @Composable (depth: Int) -> Unit
+) {
+    if (depth <= 0) {
+        content(0)
+    } else {
+        Layout(
+            content = {
+                content(depth)
+                MatryoshkaLayout(depth - 1, content)
+            },
+            measurePolicy = MinimalBoxMeasurePolicy
+        )
+    }
+}
+
+private val MinimalBoxMeasurePolicy = MeasurePolicy { measurables, constraints ->
+    val placeables = measurables.map { it.measure(constraints) }
+    val (usedWidth, usedHeight) = placeables.fold(
+        initial = IntOffset(0, 0)
+    ) { (maxWidth, maxHeight), placeable ->
+        IntOffset(
+            maxOf(maxWidth, placeable.measuredWidth),
+            maxOf(maxHeight, placeable.measuredHeight)
+        )
+    }
+
+    layout(
+        width = usedWidth,
+        height = usedHeight
+    ) {
+        placeables.forEach { it.place(0, 0) }
+    }
+}
+
+private fun Random.nextString(length: Int = 16) = buildString(length) {
+    repeat(length) { append(nextInt('A'.code, 'z'.code).toChar()) }
+}
+
+@Suppress("UNUSED_PARAMETER") private fun use(value: Any?) {}
+@Suppress("UNUSED_PARAMETER") private fun use(value: Int) {}
+@Suppress("UNUSED_PARAMETER") private fun use(value: Long) {}
+@Suppress("UNUSED_PARAMETER") private fun use(value: Float) {}
+@Suppress("UNUSED_PARAMETER") private fun use(value: Double) {}
+@Suppress("UNUSED_PARAMETER") private fun use(value: Boolean) {}
diff --git a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
index 37dcfb0..88c4553 100644
--- a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
+++ b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/OffsetTest.kt
@@ -141,4 +141,21 @@
             -Offset(Float.NaN, Float.NaN)
         }
     }
+
+    @Test
+    fun testIsFinite() {
+        assertTrue(Offset(10.0f, 20.0f).isFinite)
+        assertTrue(Offset(0.0f, 0.0f).isFinite)
+        assertTrue(Offset(10.0f, -20.0f).isFinite)
+
+        assertFalse(Offset(10.0f, Float.POSITIVE_INFINITY).isFinite)
+        assertFalse(Offset(10.0f, Float.NEGATIVE_INFINITY).isFinite)
+        assertFalse(Offset(Float.POSITIVE_INFINITY, 20.0f).isFinite)
+        assertFalse(Offset(Float.NEGATIVE_INFINITY, 20.0f).isFinite)
+        assertFalse(Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY).isFinite)
+
+        assertFails {
+            Offset(Float.NaN, Float.NaN).isFinite
+        }
+    }
 }
diff --git a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
index 01d8f1d..a6b033c 100644
--- a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
+++ b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
@@ -16,7 +16,10 @@
 
 package androidx.compose.ui.geometry
 
-import org.junit.Assert
+import kotlin.test.assertFails
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -27,11 +30,11 @@
 
     @Test
     fun sizeTimesInt() {
-        Assert.assertEquals(
+        assertEquals(
             Size(10f, 10f),
             Size(2.5f, 2.5f) * 4f
         )
-        Assert.assertEquals(
+        assertEquals(
             Size(10f, 10f),
             4f * Size(2.5f, 2.5f)
         )
@@ -39,7 +42,7 @@
 
     @Test
     fun sizeDivInt() {
-        Assert.assertEquals(
+        assertEquals(
             Size(10f, 10f),
             Size(40f, 40f) / 4f
         )
@@ -47,24 +50,24 @@
 
     @Test
     fun sizeTimesFloat() {
-        Assert.assertEquals(Size(10f, 10f), Size(4f, 4f) * 2.5f)
-        Assert.assertEquals(Size(10f, 10f), 2.5f * Size(4f, 4f))
+        assertEquals(Size(10f, 10f), Size(4f, 4f) * 2.5f)
+        assertEquals(Size(10f, 10f), 2.5f * Size(4f, 4f))
     }
 
     @Test
     fun sizeDivFloat() {
-        Assert.assertEquals(Size(10f, 10f), Size(40f, 40f) / 4f)
+        assertEquals(Size(10f, 10f), Size(40f, 40f) / 4f)
     }
 
     @Test
     fun sizeTimesDouble() {
-        Assert.assertEquals(Size(10f, 10f), Size(4f, 4f) * 2.5f)
-        Assert.assertEquals(Size(10f, 10f), 2.5f * Size(4f, 4f))
+        assertEquals(Size(10f, 10f), Size(4f, 4f) * 2.5f)
+        assertEquals(Size(10f, 10f), 2.5f * Size(4f, 4f))
     }
 
     @Test
     fun sizeDivDouble() {
-        Assert.assertEquals(
+        assertEquals(
             Size(10f, 10f),
             Size(40f, 40f) / 4.0f
         )
@@ -73,23 +76,23 @@
     @Test
     fun testSizeCopy() {
         val size = Size(100f, 200f)
-        Assert.assertEquals(size, size.copy())
+        assertEquals(size, size.copy())
     }
 
     @Test
     fun testSizeCopyOverwriteWidth() {
         val size = Size(100f, 200f)
         val copy = size.copy(width = 50f)
-        Assert.assertEquals(50f, copy.width)
-        Assert.assertEquals(200f, copy.height)
+        assertEquals(50f, copy.width)
+        assertEquals(200f, copy.height)
     }
 
     @Test
     fun testSizeCopyOverwriteHeight() {
         val size = Size(100f, 200f)
         val copy = size.copy(height = 300f)
-        Assert.assertEquals(100f, copy.width)
-        Assert.assertEquals(300f, copy.height)
+        assertEquals(100f, copy.width)
+        assertEquals(300f, copy.height)
     }
 
     @Test
@@ -137,38 +140,61 @@
     fun testSizeLerp() {
         val size1 = Size(100f, 200f)
         val size2 = Size(300f, 500f)
-        Assert.assertEquals(Size(200f, 350f), lerp(size1, size2, 0.5f))
+        assertEquals(Size(200f, 350f), lerp(size1, size2, 0.5f))
     }
 
     @Test
     fun testIsSpecified() {
-        Assert.assertFalse(Size.Unspecified.isSpecified)
-        Assert.assertTrue(Size(1f, 1f).isSpecified)
+        assertFalse(Size.Unspecified.isSpecified)
+        assertTrue(Size(1f, 1f).isSpecified)
     }
 
     @Test
     fun testIsUnspecified() {
-        Assert.assertTrue(Size.Unspecified.isUnspecified)
-        Assert.assertFalse(Size(1f, 1f).isUnspecified)
+        assertTrue(Size.Unspecified.isUnspecified)
+        assertFalse(Size(1f, 1f).isUnspecified)
     }
 
     @Test
     fun testTakeOrElseTrue() {
-        Assert.assertTrue(Size(1f, 1f).takeOrElse { Size.Unspecified }.isSpecified)
+        assertTrue(Size(1f, 1f).takeOrElse { Size.Unspecified }.isSpecified)
     }
 
     @Test
     fun testTakeOrElseFalse() {
-        Assert.assertTrue(Size.Unspecified.takeOrElse { Size(1f, 1f) }.isSpecified)
+        assertTrue(Size.Unspecified.takeOrElse { Size(1f, 1f) }.isSpecified)
     }
 
     @Test
     fun testUnspecifiedSizeToString() {
-        Assert.assertEquals("Size.Unspecified", Size.Unspecified.toString())
+        assertEquals("Size.Unspecified", Size.Unspecified.toString())
     }
 
     @Test
     fun testSpecifiedSizeToString() {
-        Assert.assertEquals("Size(10.0, 20.0)", Size(10f, 20f).toString())
+        assertEquals("Size(10.0, 20.0)", Size(10f, 20f).toString())
+    }
+
+    @Test
+    fun testIsEmpty() {
+        assertFalse(Size(10.0f, 20.0f).isEmpty())
+        assertFalse(Size(10.0f, Float.POSITIVE_INFINITY).isEmpty())
+        assertFalse(Size(Float.POSITIVE_INFINITY, 20.0f).isEmpty())
+
+        assertTrue(Size(0.0f, 20.0f).isEmpty())
+        assertTrue(Size(10.0f, 0.0f).isEmpty())
+        assertTrue(Size(0.0f, 0.0f).isEmpty())
+        assertTrue(Size(-10.0f, 20.0f).isEmpty())
+        assertTrue(Size(10.0f, -20.0f).isEmpty())
+        assertTrue(Size(0.0f, Float.POSITIVE_INFINITY).isEmpty())
+        assertTrue(Size(Float.POSITIVE_INFINITY, 0.0f).isEmpty())
+        assertTrue(Size(0.0f, Float.NEGATIVE_INFINITY).isEmpty())
+        assertTrue(Size(Float.NEGATIVE_INFINITY, 0.0f).isEmpty())
+        assertTrue(Size(Float.NEGATIVE_INFINITY, 20.0f).isEmpty())
+        assertTrue(Size(10.0f, Float.NEGATIVE_INFINITY).isEmpty())
+
+        assertFails {
+            Size.Unspecified.isEmpty()
+        }
     }
 }
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
index 2ebbbd0..2a00b19 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
@@ -21,9 +21,11 @@
 
 // Masks everything but the sign bit
 internal const val UnsignedFloatMask = 0x7fffffffL
+internal const val DualUnsignedFloatMask = 0x7fffffff_7fffffffL
 
 // Any value greater than this is a NaN
 internal const val FloatInfinityBase = 0x7f800000L
+internal const val DualFloatInfinityBase = 0x7f800000_7f800000L
 
 // Same as Offset/Size.Unspecified.packedValue, but avoids a getstatic
 internal const val UnspecifiedPackedFloats = 0x7fc00000_7fc00000L // NaN_NaN
@@ -31,6 +33,8 @@
 // 0x80000000_80000000UL.toLong() but expressed as a const value
 // Mask for the sign bit of the two floats packed in a long
 internal const val DualFloatSignBit = -0x7fffffff_80000000L
+// Set the highest bit of each 32 bit chunk in a 64 bit word
+internal const val Uint64High32 = -0x7fffffff_80000000L
 
 // This function exists so we do *not* inline the throw. It keeps
 // the call site much smaller and since it's the slow path anyway,
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index e09f97f..459e711 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -91,7 +91,7 @@
      * x or y parameter
      */
     fun copy(x: Float = unpackFloat1(packedValue), y: Float = unpackFloat2(packedValue)) =
-        Offset(x, y)
+        Offset(packFloats(x, y))
 
     companion object {
         /**
@@ -100,26 +100,23 @@
          * This can be used to represent the origin of a coordinate space.
          */
         @Stable
-        val Zero = Offset(0.0f, 0.0f)
+        val Zero = Offset(0x0L)
 
         /**
          * An offset with infinite x and y components.
          *
-         * See also:
-         *
-         *  * [isInfinite], which checks whether either component is infinite.
-         *  * [isFinite], which checks whether both components are finite.
+         * See also [isFinite] to check whether both components are finite.
          */
         // This is included for completeness, because [Size.infinite] exists.
         @Stable
-        val Infinite = Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
+        val Infinite = Offset(DualFloatInfinityBase)
 
         /**
          * Represents an unspecified [Offset] value, usually a replacement for `null`
          * when a primitive value is desired.
          */
         @Stable
-        val Unspecified = Offset(Float.NaN, Float.NaN)
+        val Unspecified = Offset(UnspecifiedPackedFloats)
     }
 
     @Stable
@@ -193,8 +190,10 @@
             "Offset is unspecified"
         }
         return Offset(
-            unpackFloat1(packedValue) - unpackFloat1(other.packedValue),
-            unpackFloat2(packedValue) - unpackFloat2(other.packedValue),
+            packFloats(
+                unpackFloat1(packedValue) - unpackFloat1(other.packedValue),
+                unpackFloat2(packedValue) - unpackFloat2(other.packedValue)
+            )
         )
     }
 
@@ -214,8 +213,10 @@
             "Offset is unspecified"
         }
         return Offset(
-            unpackFloat1(packedValue) + unpackFloat1(other.packedValue),
-            unpackFloat2(packedValue) + unpackFloat2(other.packedValue),
+            packFloats(
+                unpackFloat1(packedValue) + unpackFloat1(other.packedValue),
+                unpackFloat2(packedValue) + unpackFloat2(other.packedValue)
+            )
         )
     }
 
@@ -232,8 +233,10 @@
             "Offset is unspecified"
         }
         return Offset(
-            unpackFloat1(packedValue) * operand,
-            unpackFloat2(packedValue) * operand,
+            packFloats(
+                unpackFloat1(packedValue) * operand,
+                unpackFloat2(packedValue) * operand
+            )
         )
     }
 
@@ -250,8 +253,10 @@
             "Offset is unspecified"
         }
         return Offset(
-            unpackFloat1(packedValue) / operand,
-            unpackFloat2(packedValue) / operand,
+            packFloats(
+                unpackFloat1(packedValue) / operand,
+                unpackFloat2(packedValue) / operand
+            )
         )
     }
 
@@ -268,8 +273,10 @@
             "Offset is unspecified"
         }
         return Offset(
-            unpackFloat1(packedValue) % operand,
-            unpackFloat2(packedValue) % operand,
+            packFloats(
+                unpackFloat1(packedValue) % operand,
+                unpackFloat2(packedValue) % operand
+            )
         )
     }
 
@@ -306,8 +313,10 @@
         "Offset is unspecified"
     }
     return Offset(
-        lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
-        lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
+        packFloats(
+            lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
+            lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
+        )
     )
 }
 
@@ -319,9 +328,11 @@
     checkPrecondition(packedValue != UnspecifiedPackedFloats) {
         "Offset is unspecified"
     }
-    val x = (packedValue shr 32) and FloatInfinityBase
-    val y = packedValue and FloatInfinityBase
-    return x != FloatInfinityBase && y != FloatInfinityBase
+    // Mask out the sign bit and do an equality check in each 32-bit lane
+    // against the "infinity base" mask (to check whether each packed float
+    // is infinite or not).
+    val v = (packedValue and DualUnsignedFloatMask) xor DualFloatInfinityBase
+    return (((v shr 1) or Uint64High32) - v) and Uint64High32 == 0L
 }
 
 /**
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
index 8bd6c27..df0c802 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
@@ -74,7 +74,7 @@
      * width or height parameter
      */
     fun copy(width: Float = unpackFloat1(packedValue), height: Float = unpackFloat2(packedValue)) =
-        Size(width, height)
+        Size(packFloats(width, height))
 
     companion object {
 
@@ -82,7 +82,7 @@
          * An empty size, one with a zero width and a zero height.
          */
         @Stable
-        val Zero = Size(0.0f, 0.0f)
+        val Zero = Size(0x0L)
 
         /**
          * A size whose [width] and [height] are unspecified. This is a sentinel
@@ -90,7 +90,7 @@
          * Access to width or height on an unspecified size is not allowed.
          */
         @Stable
-        val Unspecified = Size(Float.NaN, Float.NaN)
+        val Unspecified = Size(UnspecifiedPackedFloats)
     }
 
     /**
@@ -103,7 +103,17 @@
         if (packedValue == UnspecifiedPackedFloats) {
             throwIllegalStateException("Size is unspecified")
         }
-        return unpackFloat1(packedValue) <= 0.0f || unpackFloat2(packedValue) <= 0.0f
+        // Mask the sign bits, shift them to the right and replicate them by multiplying by -1.
+        // This will give us a mask of 0xffff_ffff for negative packed floats, and 0x0000_0000
+        // for positive packed floats. We invert the mask and do an and operation with the
+        // original value to set any negative float to 0.0f.
+        val v = packedValue and ((packedValue and DualFloatSignBit ushr 31) * -0x1).inv()
+        // At this point any negative float is set to 0, so the sign bit is  always 0.
+        // We take the 2 packed floats and "and" them together: if any of the two floats
+        // is 0.0f (either because the original value is 0.0f or because it was negative and
+        // we turned it into 0.0f with the line above), the result of the and operation will
+        // be 0 and we know our Size is empty.
+        return ((v ushr 32) and (v and 0xffffffffL)) == 0L
     }
 
     /**
@@ -119,8 +129,10 @@
             throwIllegalStateException("Size is unspecified")
         }
         return Size(
-            unpackFloat1(packedValue) * operand,
-            unpackFloat2(packedValue) * operand,
+            packFloats(
+                unpackFloat1(packedValue) * operand,
+                unpackFloat2(packedValue) * operand
+            )
         )
     }
 
@@ -137,8 +149,10 @@
             throwIllegalStateException("Size is unspecified")
         }
         return Size(
-            unpackFloat1(packedValue) / operand,
-            unpackFloat2(packedValue) / operand,
+            packFloats(
+                unpackFloat1(packedValue) / operand,
+                unpackFloat2(packedValue) / operand
+            )
         )
     }
 
@@ -221,8 +235,10 @@
         throwIllegalStateException("Offset is unspecified")
     }
     return Size(
-        lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
-        lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
+        packFloats(
+            lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
+            lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
+        )
     )
 }
 
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 515e4f5..41516b7 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -918,7 +918,7 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.graphics.Shadow lerp(androidx.compose.ui.graphics.Shadow start, androidx.compose.ui.graphics.Shadow stop, float fraction);
   }
 
-  @androidx.compose.runtime.Immutable public interface Shape {
+  @androidx.compose.runtime.Stable public interface Shape {
     method public androidx.compose.ui.graphics.Outline createOutline(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 68a8faa..9da68f8 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -990,7 +990,7 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.graphics.Shadow lerp(androidx.compose.ui.graphics.Shadow start, androidx.compose.ui.graphics.Shadow stop, float fraction);
   }
 
-  @androidx.compose.runtime.Immutable public interface Shape {
+  @androidx.compose.runtime.Stable public interface Shape {
     method public androidx.compose.ui.graphics.Outline createOutline(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index e95cbdd..8179f58 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -76,7 +76,9 @@
                 // This has stub APIs for access to legacy Android APIs, so we don't want
                 // any dependency on this module.
                 compileOnly(project(":compose:ui:ui-android-stubs"))
-                implementation("androidx.graphics:graphics-path:1.0.0-beta02")
+                // TODO: Re-pin when 1.0.1 is released
+                //implementation("androidx.graphics:graphics-path:1.0.1")
+                implementation(project(":graphics:graphics-path"))
                 implementation libs.androidx.core
                 api("androidx.annotation:annotation-experimental:1.4.0")
             }
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
index 776c8d5..c106240 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/OutlineTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -55,4 +56,44 @@
         )
         assertEquals(Rect(0f, 15f, 100f, 200f), pathOutline.bounds)
     }
+
+    @Test
+    fun testRectOutlineEquality() {
+        val outlineRect = Outline.Rectangle(Rect(1f, 2f, 3f, 4f))
+        val equalOutlineRect = Outline.Rectangle(Rect(1f, 2f, 3f, 4f))
+        val differentOutlineRect = Outline.Rectangle(Rect(4f, 3f, 2f, 1f))
+        assertEquals(outlineRect, equalOutlineRect)
+        assertNotEquals(outlineRect, differentOutlineRect)
+    }
+
+    @Test
+    fun testRoundRectOutlineEquality() {
+        val roundRectOutline = Outline.Rounded(
+            RoundRect(5f, 10f, 15f, 20f, CornerRadius(7f))
+        )
+        val equalRoundRectOutline = Outline.Rounded(
+            RoundRect(5f, 10f, 15f, 20f, CornerRadius(7f))
+        )
+        val differentRoundRectOutline = Outline.Rounded(
+            RoundRect(20f, 15f, 10f, 5f, CornerRadius(3f))
+        )
+        assertEquals(roundRectOutline, equalRoundRectOutline)
+        assertNotEquals(roundRectOutline, differentRoundRectOutline)
+    }
+
+    @Test
+    fun testPathOutlineEquality() {
+        val path = Path().apply {
+            moveTo(5f, 15f)
+            lineTo(100f, 200f)
+            lineTo(0f, 200f)
+            close()
+        }
+        val pathOutline = Outline.Generic(path)
+        val pathOutline2 = Outline.Generic(path)
+
+        // Generic outlines should only be referentially equal, as the path can change over time
+        assertEquals(pathOutline, pathOutline)
+        assertNotEquals(pathOutline, pathOutline2)
+    }
 }
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
index 089f0a6..c6dc2dc 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
@@ -1234,7 +1234,9 @@
                 val path = Path().also { it.addOval(Rect(1f, 2f, 3f, 4f)) }
                 val generic = Outline.Generic(path)
                 layer.setOutline(generic)
-                assertEquals(generic, layer.outline)
+                // We wrap the path in a different Outline object from what we pass in, so compare
+                // the paths instead of the outline instances
+                assertEquals(generic.path, (layer.outline as Outline.Generic).path)
             }
         )
     }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
index b3c7de3..0c20ed5 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
@@ -106,18 +106,8 @@
         override val bounds: Rect
             get() = path.getBounds()
 
-        override fun equals(other: Any?): Boolean {
-            if (this === other) return true
-            if (other !is Generic) return false
-
-            if (path != other.path) return false
-
-            return true
-        }
-
-        override fun hashCode(): Int {
-            return path.hashCode()
-        }
+        // No equals or hashcode, two different outlines using the same path shouldn't be considered
+        // equal as the path may have changed since the previous outline was rendered
     }
 
     /**
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
index e174944..0ce177b 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.ui.graphics
 
-import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
@@ -24,7 +24,7 @@
 /**
  * Defines a generic shape.
  */
-@Immutable
+@Stable
 interface Shape {
     /**
      * Creates [Outline] of this shape for the given [size].
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index e374fff..4e5c0b0 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -571,9 +571,9 @@
     method public static void click(androidx.compose.ui.test.TouchInjectionScope, optional long position);
     method public static void doubleClick(androidx.compose.ui.test.TouchInjectionScope, optional long position, optional long delayMillis);
     method public static void longClick(androidx.compose.ui.test.TouchInjectionScope, optional long position, optional long durationMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void multiTouchSwipe(androidx.compose.ui.test.TouchInjectionScope, java.util.List<? extends kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset>> curves, long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void multiTouchSwipe(androidx.compose.ui.test.TouchInjectionScope, java.util.List<? extends kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset>> curves, optional long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
     method public static void pinch(androidx.compose.ui.test.TouchInjectionScope, long start0, long end0, long start1, long end1, optional long durationMillis);
-    method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset> curve, long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
+    method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset> curve, optional long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
     method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, long start, long end, optional long durationMillis);
     method public static void swipeDown(androidx.compose.ui.test.TouchInjectionScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeLeft(androidx.compose.ui.test.TouchInjectionScope, optional float startX, optional float endX, optional long durationMillis);
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index fb5c367..fce6f17 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -572,9 +572,9 @@
     method public static void click(androidx.compose.ui.test.TouchInjectionScope, optional long position);
     method public static void doubleClick(androidx.compose.ui.test.TouchInjectionScope, optional long position, optional long delayMillis);
     method public static void longClick(androidx.compose.ui.test.TouchInjectionScope, optional long position, optional long durationMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void multiTouchSwipe(androidx.compose.ui.test.TouchInjectionScope, java.util.List<? extends kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset>> curves, long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void multiTouchSwipe(androidx.compose.ui.test.TouchInjectionScope, java.util.List<? extends kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset>> curves, optional long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
     method public static void pinch(androidx.compose.ui.test.TouchInjectionScope, long start0, long end0, long start1, long end1, optional long durationMillis);
-    method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset> curve, long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
+    method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, kotlin.jvm.functions.Function1<? super java.lang.Long,androidx.compose.ui.geometry.Offset> curve, optional long durationMillis, optional java.util.List<java.lang.Long> keyTimes);
     method public static void swipe(androidx.compose.ui.test.TouchInjectionScope, long start, long end, optional long durationMillis);
     method public static void swipeDown(androidx.compose.ui.test.TouchInjectionScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeLeft(androidx.compose.ui.test.TouchInjectionScope, optional float startX, optional float endX, optional long durationMillis);
diff --git a/compose/ui/ui-test/lint-baseline.xml b/compose/ui/ui-test/lint-baseline.xml
index 40df33c..199ff09 100644
--- a/compose/ui/ui-test/lint-baseline.xml
+++ b/compose/ui/ui-test/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
 
     <issue
         id="BanThreadSleep"
diff --git a/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/TouchInjectionScopeSamples.kt b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/TouchInjectionScopeSamples.kt
index 6b948c8..29cd365 100644
--- a/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/TouchInjectionScopeSamples.kt
+++ b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/TouchInjectionScopeSamples.kt
@@ -17,6 +17,8 @@
 package androidx.compose.ui.test.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.onNodeWithTag
@@ -76,3 +78,35 @@
             up()
         }
 }
+
+@Sampled
+fun touchInputMultiTouchWithHistory() {
+    // Move two fingers in a horizontal line, one on y=100 and one on y=500
+    composeTestRule.onNodeWithTag("myComponent")
+        .performTouchInput {
+            // First, make contact with the screen with both pointers:
+            down(0, Offset(300f, 100f))
+            down(1, Offset(300f, 500f))
+            // Update the pointer locations for the next event
+            updatePointerTo(0, Offset(400f, 100f))
+            updatePointerTo(1, Offset(400f, 500f))
+            // And send the move event with historical data
+            @OptIn(ExperimentalTestApi::class)
+            moveWithHistoryMultiPointer(
+                // Let's add 3 historical events
+                relativeHistoricalTimes = listOf(-12, -8, -4),
+                // Now, for each pointer we supply the historical coordinates
+                historicalCoordinates = listOf(
+                    // Pointer 0 moves along y=100
+                    listOf(Offset(325f, 100f), Offset(350f, 100f), Offset(375f, 100f)),
+                    // Pointer 1 moves along y=500
+                    listOf(Offset(325f, 500f), Offset(350f, 500f), Offset(375f, 500f)),
+                ),
+                // The actual move event will be sent 16ms after the previous event
+                delayMillis = 16
+            )
+            // And finish the gesture by lifting both fingers. Can be done in any order
+            up(1)
+            up(0)
+        }
+}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/SwipeMultiTouchTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/SwipeMultiTouchTest.kt
new file mode 100644
index 0000000..3b0a4b8
--- /dev/null
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/SwipeMultiTouchTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.compose.ui.test.injectionscope.touch
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventType.Companion.Move
+import androidx.compose.ui.input.pointer.PointerEventType.Companion.Press
+import androidx.compose.ui.input.pointer.PointerEventType.Companion.Release
+import androidx.compose.ui.input.pointer.PointerId
+import androidx.compose.ui.input.pointer.PointerType.Companion.Touch
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.multiTouchSwipe
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.runComposeUiTest
+import androidx.compose.ui.test.util.ClickableTestBox
+import androidx.compose.ui.test.util.SinglePointerInputRecorder
+import androidx.compose.ui.test.util.verify
+import androidx.compose.ui.test.util.verifyEvents
+import androidx.test.filters.MediumTest
+import org.junit.Test
+
+@MediumTest
+@OptIn(ExperimentalTestApi::class)
+class SwipeMultiTouchTest {
+    companion object {
+        private const val TAG = "widget"
+        // Duration is 4 * eventPeriod to get easily predictable results
+        private const val DURATION = 64L
+    }
+
+    private val recorder = SinglePointerInputRecorder()
+
+    @Test
+    fun test() = runComposeUiTest {
+        setContent {
+            Box(Modifier.fillMaxSize()) {
+                ClickableTestBox(modifier = recorder, tag = TAG)
+            }
+        }
+
+        // Move three fingers over the box from left to right simultaneously
+        // With a duration that is exactly 4 times the eventPeriod, each pointer will be sampled
+        // at t = 0, 16, 32, 48 and 64. That corresponds to x values of 10, 30, 50, 70 and 90.
+
+        val curve1 = line(fromX = 10f, toX = 90f, y = 20f, DURATION)
+        val curve2 = line(fromX = 10f, toX = 90f, y = 50f, DURATION)
+        val curve3 = line(fromX = 10f, toX = 90f, y = 80f, DURATION)
+
+        onNodeWithTag(TAG).performTouchInput {
+            multiTouchSwipe(
+                curves = listOf(curve1, curve2, curve3),
+                durationMillis = DURATION
+            )
+        }
+
+        val pointer1 = PointerId(0)
+        val pointer2 = PointerId(1)
+        val pointer3 = PointerId(2)
+
+        runOnIdle {
+            recorder.apply {
+                verifyEvents(
+                    // pointer1 down
+                    { verify(0L, pointer1, true, Offset(10f, 20f), Touch, Press) },
+                    // pointer2 down
+                    { verify(0L, pointer1, true, Offset(10f, 20f), Touch, Press) },
+                    { verify(0L, pointer2, true, Offset(10f, 50f), Touch, Press) },
+                    // pointer3 down
+                    { verify(0L, pointer1, true, Offset(10f, 20f), Touch, Press) },
+                    { verify(0L, pointer2, true, Offset(10f, 50f), Touch, Press) },
+                    { verify(0L, pointer3, true, Offset(10f, 80f), Touch, Press) },
+                    // first move
+                    { verify(16L, pointer1, true, Offset(30f, 20f), Touch, Move) },
+                    { verify(16L, pointer2, true, Offset(30f, 50f), Touch, Move) },
+                    { verify(16L, pointer3, true, Offset(30f, 80f), Touch, Move) },
+                    // second move
+                    { verify(32L, pointer1, true, Offset(50f, 20f), Touch, Move) },
+                    { verify(32L, pointer2, true, Offset(50f, 50f), Touch, Move) },
+                    { verify(32L, pointer3, true, Offset(50f, 80f), Touch, Move) },
+                    // third move
+                    { verify(48L, pointer1, true, Offset(70f, 20f), Touch, Move) },
+                    { verify(48L, pointer2, true, Offset(70f, 50f), Touch, Move) },
+                    { verify(48L, pointer3, true, Offset(70f, 80f), Touch, Move) },
+                    // last move
+                    { verify(64L, pointer1, true, Offset(90f, 20f), Touch, Move) },
+                    { verify(64L, pointer2, true, Offset(90f, 50f), Touch, Move) },
+                    { verify(64L, pointer3, true, Offset(90f, 80f), Touch, Move) },
+                    // pointer1 up
+                    { verify(64L, pointer1, false, Offset(90f, 20f), Touch, Release) },
+                    { verify(64L, pointer2, true, Offset(90f, 50f), Touch, Release) },
+                    { verify(64L, pointer3, true, Offset(90f, 80f), Touch, Release) },
+                    // pointer2 up
+                    { verify(64L, pointer2, false, Offset(90f, 50f), Touch, Release) },
+                    { verify(64L, pointer3, true, Offset(90f, 80f), Touch, Release) },
+                    // pointer3 up
+                    { verify(64L, pointer3, false, Offset(90f, 80f), Touch, Release) },
+                )
+            }
+        }
+    }
+
+    @Suppress("SameParameterValue")
+    private fun line(fromX: Float, toX: Float, y: Float, durationMillis: Long): (Long) -> Offset {
+        return { Offset(fromX + (toX - fromX) * it / durationMillis, y) }
+    }
+}
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
index 71d02be..2f412d9 100644
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt
@@ -438,7 +438,8 @@
             Offset.Zero, 0, expectedMetaState = expectedMetaState)
 
         // Key Toggle Off
-        recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode)
+        recorder.events[8].verifyKeyEvent(keyDown, key.nativeKeyCode,
+            expectedMetaState = expectedMetaState)
         recorder.events[9].verifyKeyEvent(keyUp, key.nativeKeyCode)
 
         // Mouse Press
diff --git a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
index 0216a50..4d1f794 100644
--- a/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyEventsTest.kt
@@ -514,7 +514,8 @@
         recorder.events[1].verifyKeyEvent(
             keyUp, key.nativeKeyCode, expectedMetaState = expectedMetaState
         )
-        recorder.events[2].verifyKeyEvent(keyDown, key.nativeKeyCode)
+        recorder.events[2].verifyKeyEvent(keyDown, key.nativeKeyCode,
+            expectedMetaState = expectedMetaState)
         recorder.events[3].verifyKeyEvent(keyUp, key.nativeKeyCode)
     }
 }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
index 1723384..1d17c65 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
@@ -705,6 +705,30 @@
 
     protected abstract fun KeyInputState.enqueueUp(key: Key)
 
+    /**
+     * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+     * toggling. To change toggling behaviour, override this method and switch to using
+     * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+     */
+    protected open val KeyInputState.capsLockOn: Boolean
+        get() = capsLockState.isLockKeyOnIncludingOffPress
+
+    /**
+     * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+     * toggling. To change toggling behaviour, override this method and switch to using
+     * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+     */
+    protected open val KeyInputState.numLockOn: Boolean
+        get() = numLockState.isLockKeyOnIncludingOffPress
+
+    /**
+     * Used to control lock key toggling behaviour on different platforms. Defaults to Android-style
+     * toggling. To change toggling behaviour, override this method and switch to using
+     * [LockKeyState.isLockKeyOnExcludingOffPress], or implement a different toggling behaviour.
+     */
+    protected open val KeyInputState.scrollLockOn: Boolean
+        get() = scrollLockState.isLockKeyOnIncludingOffPress
+
     @OptIn(ExperimentalTestApi::class)
     protected abstract fun MouseInputState.enqueueScroll(delta: Float, scrollWheel: ScrollWheel)
 
@@ -778,6 +802,56 @@
 }
 
 /**
+ * Toggling states for lock keys.
+ *
+ * Note that lock keys may not be toggled in the same way across all platforms.
+ *
+ * Take caps lock as an example; consistently, all platforms turn caps lock on upon the first
+ * key down event, and it stays on after the subsequent key up. However, on some platforms caps
+ * lock will turn off immediately upon the next key down event (MacOS for example), whereas
+ * other platforms (e.g. Linux, Android) wait for the next key up event before turning caps
+ * lock off.
+ *
+ * This enum breaks the lock key state down into four possible options - depending upon the
+ * interpretation of these four states, Android-like or MacOS-like behaviour can both be achieved.
+ *
+ * To get Android-like behaviour, use [isLockKeyOnIncludingOffPress],
+ * whereas for MacOS-style behaviour, use [isLockKeyOnExcludingOffPress].
+ */
+internal enum class LockKeyState(val state: Int) {
+    UP_AND_OFF(0),
+    DOWN_AND_ON(1),
+    UP_AND_ON(2),
+    DOWN_AND_OPTIONAL(3);
+
+    /**
+     * Whether or not the lock key is on. The lock key is considered on from the start of the
+     * "on press" until the end of the "off press", i.e. from the first key down event to the
+     * second key up event of the corresponding lock key.
+     */
+    val isLockKeyOnIncludingOffPress get() = state > 0
+
+    /**
+     * Whether or not the lock key is on. The lock key is considered on from the start of the
+     * "on press" until the start of the "off press", i.e. from the first key down event to the
+     * second key down event of the corresponding lock key.
+     */
+    val isLockKeyOnExcludingOffPress get() = this == DOWN_AND_ON || this == UP_AND_ON
+
+    /**
+     * Returns the next state in the cycle of lock key states.
+     */
+    fun next(): LockKeyState {
+        return when (this) {
+            UP_AND_OFF -> DOWN_AND_ON
+            DOWN_AND_ON -> UP_AND_ON
+            UP_AND_ON -> DOWN_AND_OPTIONAL
+            DOWN_AND_OPTIONAL -> UP_AND_OFF
+        }
+    }
+}
+
+/**
  * The current key input state. Contains the keys that are pressed, the down time of the
  * keyboard (which is the time of the last key down event), the state of the lock keys and
  * the device ID.
@@ -789,9 +863,9 @@
     var repeatKey: Key? = null
     var repeatCount = 0
     var lastRepeatTime = downTime
-    var capsLockOn = false
-    var numLockOn = false
-    var scrollLockOn = false
+    var capsLockState: LockKeyState = LockKeyState.UP_AND_OFF
+    var numLockState: LockKeyState = LockKeyState.UP_AND_OFF
+    var scrollLockState: LockKeyState = LockKeyState.UP_AND_OFF
 
     fun isKeyDown(key: Key): Boolean = downKeys.contains(key)
 
@@ -801,6 +875,7 @@
             repeatKey = null
             repeatCount = 0
         }
+        updateLockKeys(key)
     }
 
     fun setKeyDown(key: Key) {
@@ -812,23 +887,12 @@
 
     /**
      * Updates lock key state values.
-     *
-     * Note that lock keys may not be toggled in the same way across all platforms.
-     *
-     * Take caps lock as an example; consistently, all platforms turn caps lock on upon the first
-     * key down event, and it stays on after the subsequent key up. However, on some platforms caps
-     * lock will turn off immediately upon the next key down event (MacOS for example), whereas
-     * other platforms (e.g. linux) wait for the next key up event before turning caps lock off.
-     *
-     * By calling this function whenever a lock key is pressed down, MacOS-like behaviour is
-     * achieved.
      */
-    // TODO(Onadim): Investigate how lock key toggling is handled in Android, ChromeOS and Windows.
     private fun updateLockKeys(key: Key) {
         when (key) {
-            Key.CapsLock -> capsLockOn = !capsLockOn
-            Key.NumLock -> numLockOn = !numLockOn
-            Key.ScrollLock -> scrollLockOn = !scrollLockOn
+            Key.CapsLock -> capsLockState = capsLockState.next()
+            Key.NumLock -> numLockState = numLockState.next()
+            Key.ScrollLock -> scrollLockState = scrollLockState.next()
         }
     }
 }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TouchInjectionScope.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TouchInjectionScope.kt
index d814f96..5992018 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TouchInjectionScope.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TouchInjectionScope.kt
@@ -234,15 +234,18 @@
 
     /**
      * Sends a move event [delayMillis] after the last sent event without updating any of the
-     * pointer positions.
+     * pointer positions, while adding the [historicalCoordinates] at the [relativeHistoricalTimes]
+     * to the move event. This corresponds to the scenario where a touch screen generates touch
+     * events quicker than can be dispatched and batches them together.
      *
-     * This overload supports gestures with multiple pointers.
+     * @sample androidx.compose.ui.test.samples.touchInputMultiTouchWithHistory
      *
      * @param relativeHistoricalTimes Time of each historical event, as a millisecond relative to
      * the time the actual event is sent. For example, -10L means 10ms earlier.
      * @param historicalCoordinates Coordinates of each historical event, in the same coordinate
      * space as [moveTo]. The outer list must have the same size as the number of pointers in the
-     * event, and each inner list must have the same size as [relativeHistoricalTimes].
+     * event, and each inner list must have the same size as [relativeHistoricalTimes]. The `i`th
+     * pointer is assigned the `i`th history, with the pointers sorted on ascending pointerId.
      * @param delayMillis The time between the last sent event and this event.
      * [eventPeriodMillis] by default.
      */
@@ -255,7 +258,9 @@
 
     /**
      * Sends a move event [delayMillis] after the last sent event without updating any of the
-     * pointer positions.
+     * pointer positions, while adding the [historicalCoordinates] at the [relativeHistoricalTimes]
+     * to the move event. This corresponds to the scenario where a touch screen generates touch
+     * events quicker than can be dispatched and batches them together.
      *
      * This overload is a convenience method for the common case where the gesture only has one
      * pointer.
@@ -464,7 +469,7 @@
  */
 fun TouchInjectionScope.swipe(
     curve: (Long) -> Offset,
-    durationMillis: Long,
+    durationMillis: Long = 200,
     keyTimes: List<Long> = emptyList()
 ) {
     @OptIn(ExperimentalTestApi::class)
@@ -491,7 +496,7 @@
 @ExperimentalTestApi
 fun TouchInjectionScope.multiTouchSwipe(
     curves: List<(Long) -> Offset>,
-    durationMillis: Long,
+    durationMillis: Long = 200,
     keyTimes: List<Long> = emptyList()
 ) {
     val startTime = 0L
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
index 8cf9efd..b5f6bde 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
@@ -208,10 +208,10 @@
      * @param textLayoutResult the text field's [TextLayoutResult]
      * @param textFieldToRootTransform function that modifies a matrix to be a transformation matrix
      *   from local coordinates to the root composable coordinates
-     * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an
-     *   empty rectangle if the text field is not visible
-     * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an
-     *   empty rectangle if the decoration box is not visible
+     * @param innerTextFieldBounds visible bounds of the text field in text layout coordinates, or
+     *   an empty rectangle if the text field is not visible
+     * @param decorationBoxBounds visible bounds of the decoration box in text layout coordinates,
+     *   or an empty rectangle if the decoration box is not visible
      */
     fun updateTextLayoutResult(
         textFieldValue: TextFieldValue,
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 2ca13db..ad00359 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -396,7 +396,9 @@
             val scope = ReusableGraphicsLayerScope()
             scope.cameraDistance = cameraDistance
             scope.compositingStrategy = compositingStrategy
-            updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+            scope.layoutDirection = LayoutDirection.Ltr
+            scope.graphicsDensity = Density(1f)
+            updateLayerProperties(scope)
         }
         return expectedLayerType == view.layerType &&
             expectedOverlappingRendering == view.hasOverlappingRendering()
@@ -436,7 +438,9 @@
         ).apply {
             val scope = ReusableGraphicsLayerScope()
             scope.cameraDistance = cameraDistance
-            updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+            scope.layoutDirection = LayoutDirection.Ltr
+            scope.graphicsDensity = Density(1f)
+            updateLayerProperties(scope)
         }
         // Verify that the camera distance is applied properly even after accounting for
         // the internal dp conversion within View
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
index 6bc2204..490bb83 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
@@ -47,6 +47,7 @@
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.PathOperation
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.addOutline
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.toArgb
@@ -471,6 +472,126 @@
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
+    fun switchBetweenDifferentOutlines_differentPath_observableShape() {
+        var invertedTriangle by mutableStateOf(false)
+        val observableShape = object : Shape {
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ): Outline {
+              return if (invertedTriangle) {
+                  invertedTriangleShape.createOutline(size, layoutDirection, density)
+              } else {
+                  triangleShape.createOutline(size, layoutDirection, density)
+              }
+            }
+        }
+        // to be replaced with a DrawModifier wrapped into remember, so the recomposition
+        // is not causing invalidation as the DrawModifier didn't change
+        val drawCallback: DrawScope.() -> Unit = {
+            drawRect(
+                Color.Cyan,
+                topLeft = Offset(-100f, -100f),
+                size = Size(size.width + 200f, size.height + 200f)
+            )
+        }
+
+        val clip = Modifier.graphicsLayer {
+            shape = observableShape
+            clip = true
+            drawLatch.countDown()
+        }
+
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                AtLeastSize(
+                    size = 30,
+                    modifier = Modifier
+                        .background(Color.Green)
+                        .then(clip)
+                        .drawBehind(drawCallback)
+                ) {
+                }
+            }
+        }
+
+        takeScreenShot(30).apply {
+            assertTriangle(Color.Cyan, Color.Green)
+        }
+
+        drawLatch = CountDownLatch(1)
+        rule.runOnUiThreadIR { invertedTriangle = true }
+
+        takeScreenShot(30).apply {
+            assertInvertedTriangle(Color.Cyan, Color.Green)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun switchBetweenDifferentOutlines_samePath_observableShape() {
+        var invertedTriangle by mutableStateOf(false)
+        val path = Path()
+        val observableShape = object : Shape {
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ): Outline {
+                val outline = if (invertedTriangle) {
+                    invertedTriangleShape.createOutline(size, layoutDirection, density)
+                } else {
+                    triangleShape.createOutline(size, layoutDirection, density)
+                }
+                path.reset()
+                path.addOutline(outline)
+                return Outline.Generic(path)
+            }
+        }
+        // to be replaced with a DrawModifier wrapped into remember, so the recomposition
+        // is not causing invalidation as the DrawModifier didn't change
+        val drawCallback: DrawScope.() -> Unit = {
+            drawRect(
+                Color.Cyan,
+                topLeft = Offset(-100f, -100f),
+                size = Size(size.width + 200f, size.height + 200f)
+            )
+        }
+
+        val clip = Modifier.graphicsLayer {
+            shape = observableShape
+            clip = true
+            drawLatch.countDown()
+        }
+
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                AtLeastSize(
+                    size = 30,
+                    modifier = Modifier
+                        .background(Color.Green)
+                        .then(clip)
+                        .drawBehind(drawCallback)
+                ) {
+                }
+            }
+        }
+
+        takeScreenShot(30).apply {
+            assertTriangle(Color.Cyan, Color.Green)
+        }
+
+        drawLatch = CountDownLatch(1)
+        rule.runOnUiThreadIR { invertedTriangle = true }
+
+        takeScreenShot(30).apply {
+            assertInvertedTriangle(Color.Cyan, Color.Green)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
     fun emitClipLater() {
         val model = mutableStateOf(false)
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index d1b40df..c63e2a9 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -4172,12 +4172,7 @@
         explicitLayer: GraphicsLayer?
     ): OwnedLayer {
         return object : OwnedLayer {
-            override fun updateLayerProperties(
-                scope: ReusableGraphicsLayerScope,
-                layoutDirection: LayoutDirection,
-                density: Density
-            ) {
-            }
+            override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {}
 
             override fun isInLayer(position: Offset) = true
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index e8557cf..e0f3bf0 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -570,12 +570,7 @@
 
 internal open class MockLayer() : OwnedLayer {
 
-    override fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density
-    ) {
-    }
+    override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {}
 
     override fun isInLayer(position: Offset) = true
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
index c2ec417..339d19f 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
@@ -508,11 +508,7 @@
                 drawBlock: (Canvas, GraphicsLayer?) -> Unit,
                 invalidateParentLayer: () -> Unit
             ) {}
-            override fun updateLayerProperties(
-                scope: ReusableGraphicsLayerScope,
-                layoutDirection: LayoutDirection,
-                density: Density
-            ) {
+            override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
                 transform.reset()
                 // This is not expected to be 100% accurate
                 transform.scale(scope.scaleX, scope.scaleY)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 12737fd..8399995 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -28,9 +28,7 @@
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
 import androidx.compose.ui.graphics.drawscope.draw
@@ -80,7 +78,7 @@
     private val scope = CanvasDrawScope()
     private var mutatedFields: Int = 0
     private var transformOrigin: TransformOrigin = TransformOrigin.Center
-    private var shape: Shape = RectangleShape
+    private var outline: Outline? = null
     private var tmpPath: Path? = null
     /**
      * Optional paint used when the RenderNode is rendered on a software backed
@@ -88,17 +86,10 @@
      */
     private var softwareLayerPaint: Paint? = null
 
-    override fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density,
-    ) {
-        var maybeChangedFields = scope.mutatedFields or mutatedFields
-        if (this.layoutDirection != layoutDirection || this.density != density) {
-            this.layoutDirection = layoutDirection
-            this.density = density
-            maybeChangedFields = maybeChangedFields or Fields.Shape
-        }
+    override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
+        val maybeChangedFields = scope.mutatedFields or mutatedFields
+        this.layoutDirection = scope.layoutDirection
+        this.density = scope.graphicsDensity
         if (maybeChangedFields and Fields.TransformOrigin != 0) {
             this.transformOrigin = scope.transformOrigin
         }
@@ -152,10 +143,6 @@
                 transformOrigin.pivotFractionY * size.height
             )
         }
-        if (maybeChangedFields and Fields.Shape != 0) {
-            this.shape = scope.shape
-            updateOutline()
-        }
         if (maybeChangedFields and Fields.Clip != 0) {
             graphicsLayer.clip = scope.clip
         }
@@ -171,8 +158,16 @@
             }
         }
 
+        var outlineChanged = false
+
+        if (outline != scope.outline) {
+            outlineChanged = true
+            outline = scope.outline
+            updateOutline()
+        }
+
         mutatedFields = scope.mutatedFields
-        if (maybeChangedFields != 0) {
+        if (maybeChangedFields != 0 || outlineChanged) {
             triggerRepaint()
         }
     }
@@ -189,7 +184,7 @@
     }
 
     private fun updateOutline() {
-        val outline = shape.createOutline(size.toSize(), layoutDirection, density)
+        val outline = outline ?: return
         graphicsLayer.setOutline(outline)
         if (outline is Outline.Generic && Build.VERSION.SDK_INT < 33) {
             // before 33 many of the paths are not clipping by rendernode. instead we have to
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
index 7f15fef..30d26a5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.android.kt
@@ -27,17 +27,13 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.asAndroidPath
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastRoundToInt
 
 /**
- * Resolves the [AndroidOutline] from the [Shape] of an [OwnedLayer].
+ * Resolves the [AndroidOutline] from the [Outline] of an [androidx.compose.ui.node.OwnedLayer].
  */
-internal class OutlineResolver(private var density: Density) {
+internal class OutlineResolver {
 
     /**
      * Flag to determine if the shape specified on the outline is supported.
@@ -51,14 +47,9 @@
     private val cachedOutline = AndroidOutline().apply { alpha = 1f }
 
     /**
-     * The size of the layer. This is used in generating the [Outline] from the [Shape].
+     * The [Outline] of the Layer.
      */
-    private var size: Size = Size.Zero
-
-    /**
-     * The [Shape] of the Outline of the Layer.
-     */
-    private var shape: Shape = RectangleShape
+    private var outline: Outline? = null
 
     /**
      * Asymmetric rounded rectangles need to use a Path. This caches that Path so that
@@ -107,7 +98,7 @@
     /**
      * Returns the Android Outline to be used in the layer.
      */
-    val outline: AndroidOutline?
+    val androidOutline: AndroidOutline?
         get() {
             updateCache()
             return if (!outlineNeeded || !isSupportedOutline) null else cachedOutline
@@ -145,7 +136,6 @@
     /**
      * Returns the size for a rectangular, or rounded rect outline (regardless if it
      * is symmetric or asymmetric)
-     * For path based outlines this returns [Size.Zero]
      */
     private var rectSize: Size = Size.Zero
 
@@ -154,43 +144,32 @@
      */
     private var outlineNeeded = false
 
-    private var layoutDirection = LayoutDirection.Ltr
-
     private var tmpTouchPointPath: Path? = null
     private var tmpOpPath: Path? = null
-    private var calculatedOutline: Outline? = null
 
     /**
      * Updates the values of the outline. Returns `true` when the shape has changed.
      */
     fun update(
-        shape: Shape,
+        outline: Outline?,
         alpha: Float,
         clipToOutline: Boolean,
         elevation: Float,
-        layoutDirection: LayoutDirection,
-        density: Density
+        size: Size,
     ): Boolean {
         cachedOutline.alpha = alpha
-        val shapeChanged = this.shape != shape
-        if (shapeChanged) {
-            this.shape = shape
+        val outlineChanged = this.outline != outline
+        if (outlineChanged) {
+            this.outline = outline
             cacheIsDirty = true
         }
-        val outlineNeeded = clipToOutline || elevation > 0f
+        this.rectSize = size
+        val outlineNeeded = outline != null && (clipToOutline || elevation > 0f)
         if (this.outlineNeeded != outlineNeeded) {
             this.outlineNeeded = outlineNeeded
             cacheIsDirty = true
         }
-        if (this.layoutDirection != layoutDirection) {
-            this.layoutDirection = layoutDirection
-            cacheIsDirty = true
-        }
-        if (this.density != density) {
-            this.density = density
-            cacheIsDirty = true
-        }
-        return shapeChanged
+        return outlineChanged
     }
 
     /**
@@ -200,7 +179,7 @@
         if (!outlineNeeded) {
             return true
         }
-        val outline = calculatedOutline ?: return true
+        val outline = outline ?: return true
 
         return isInOutline(outline, position.x, position.y, tmpTouchPointPath, tmpOpPath)
     }
@@ -256,31 +235,20 @@
         }
     }
 
-    /**
-     * Updates the size.
-     */
-    fun update(size: Size) {
-        if (this.size != size) {
-            this.size = size
-            cacheIsDirty = true
-        }
-    }
-
     private fun updateCache() {
         if (cacheIsDirty) {
             rectTopLeft = Offset.Zero
-            rectSize = size
             roundedCornerRadius = 0f
             outlinePath = null
             cacheIsDirty = false
             usePathForClip = false
-            if (outlineNeeded && size.width > 0.0f && size.height > 0.0f) {
+            val outline = outline
+            if (outline != null && outlineNeeded &&
+                rectSize.width > 0.0f && rectSize.height > 0.0f) {
                 // Always assume the outline type is supported
                 // The methods to configure the outline will determine/update the flag
                 // if it not supported on the API level
                 isSupportedOutline = true
-                val outline = shape.createOutline(size, layoutDirection, density)
-                calculatedOutline = outline
                 when (outline) {
                     is Outline.Rectangle -> updateCacheWithRect(outline.rect)
                     is Outline.Rounded -> updateCacheWithRoundRect(outline.roundRect)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
index b1c7718..2e6c0be 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
@@ -19,10 +19,8 @@
 import android.os.Build
 import android.view.View
 import androidx.annotation.RequiresApi
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.CanvasHolder
 import androidx.compose.ui.graphics.Fields
@@ -36,10 +34,8 @@
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.OwnedLayer
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * RenderNode implementation of OwnedLayer.
@@ -63,11 +59,7 @@
                 ownerView.notifyLayerIsDirty(this, value)
             }
         }
-    private val outlineResolver = Snapshot.withoutReadObservation {
-        // we don't really care about observation here as density is applied manually
-        // not observing the density changes saves performance on recording reads
-        OutlineResolver(ownerView.density)
-    }
+    private val outlineResolver = OutlineResolver()
     private var isDestroyed = false
     private var drawnWithZ = false
 
@@ -117,11 +109,7 @@
 
     private var mutatedFields: Int = 0
 
-    override fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density,
-    ) {
+    override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
         val maybeChangedFields = scope.mutatedFields or mutatedFields
         if (maybeChangedFields and Fields.TransformOrigin != 0) {
             this.transformOrigin = scope.transformOrigin
@@ -179,15 +167,14 @@
             renderNode.compositingStrategy = scope.compositingStrategy
         }
         val shapeChanged = outlineResolver.update(
-            scope.shape,
+            scope.outline,
             scope.alpha,
             clipToOutline,
             scope.shadowElevation,
-            layoutDirection,
-            density
+            scope.size,
         )
         if (outlineResolver.cacheIsDirty) {
-            renderNode.setOutline(outlineResolver.outline)
+            renderNode.setOutline(outlineResolver.androidOutline)
         }
         val isClippingManually = clipToOutline && !outlineResolver.outlineClipSupported
         if (wasClippingManually != isClippingManually || (isClippingManually && shapeChanged)) {
@@ -232,8 +219,7 @@
                 renderNode.top + height
             )
         ) {
-            outlineResolver.update(Size(width.toFloat(), height.toFloat()))
-            renderNode.setOutline(outlineResolver.outline)
+            renderNode.setOutline(outlineResolver.androidOutline)
             invalidate()
             matrixCache.invalidate()
         }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
index 0ccc913..6e820de 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
@@ -21,10 +21,8 @@
 import android.view.View
 import android.view.ViewOutlineProvider
 import androidx.annotation.RequiresApi
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.CanvasHolder
 import androidx.compose.ui.graphics.CompositingStrategy
@@ -39,10 +37,8 @@
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.OwnedLayer
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 import java.lang.reflect.Field
 import java.lang.reflect.Method
 
@@ -58,11 +54,7 @@
     private var drawBlock: ((canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit)? = drawBlock
     private var invalidateParentLayer: (() -> Unit)? = invalidateParentLayer
 
-    private val outlineResolver = Snapshot.withoutReadObservation {
-        // we don't really care about observation here as density is applied manually
-        // not observing the density changes saves performance on recording reads
-        OutlineResolver(ownerView.density)
-    }
+    private val outlineResolver = OutlineResolver()
     // Value of the layerModifier's clipToBounds property
     private var clipToBounds = false
     private var clipBoundsCache: android.graphics.Rect? = null
@@ -132,11 +124,7 @@
 
     private var mutatedFields: Int = 0
 
-    override fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density,
-    ) {
+    override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
         val maybeChangedFields = scope.mutatedFields or mutatedFields
         if (maybeChangedFields and Fields.TransformOrigin != 0) {
             this.mTransformOrigin = scope.transformOrigin
@@ -181,12 +169,11 @@
             this.clipToOutline = clipToOutline
         }
         val shapeChanged = outlineResolver.update(
-            scope.shape,
+            scope.outline,
             scope.alpha,
             clipToOutline,
             scope.shadowElevation,
-            layoutDirection,
-            density
+            scope.size
         )
         if (outlineResolver.cacheIsDirty) {
             updateOutlineResolver()
@@ -261,7 +248,7 @@
     }
 
     private fun updateOutlineResolver() {
-        this.outlineProvider = if (outlineResolver.outline != null) {
+        this.outlineProvider = if (outlineResolver.androidOutline != null) {
             OutlineProvider
         } else {
             null
@@ -287,7 +274,6 @@
         if (width != this.width || height != this.height) {
             pivotX = mTransformOrigin.pivotFractionX * width
             pivotY = mTransformOrigin.pivotFractionY * height
-            outlineResolver.update(Size(width.toFloat(), height.toFloat()))
             updateOutlineResolver()
             layout(left, top, left + width, top + height)
             resetClipBounds()
@@ -437,7 +423,7 @@
         val OutlineProvider = object : ViewOutlineProvider() {
             override fun getOutline(view: View, outline: android.graphics.Outline) {
                 view as ViewLayer
-                outline.set(view.outlineResolver.outline!!)
+                outline.set(view.outlineResolver.androidOutline!!)
             }
         }
         private var updateDisplayListIfDirtyMethod: Method? = null
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index bb76634..cd0eedd 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -2688,11 +2688,7 @@
         val transform = Matrix()
         val inverseTransform = Matrix()
         return object : OwnedLayer {
-            override fun updateLayerProperties(
-                scope: ReusableGraphicsLayerScope,
-                layoutDirection: LayoutDirection,
-                density: Density
-            ) {
+            override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
                 transform.reset()
                 // This is not expected to be 100% accurate
                 transform.scale(scope.scaleX, scope.scaleY)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
index 9cf5728..ca8fd83 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.graphics
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ComposableOpenTarget
 import androidx.compose.runtime.RememberObserver
@@ -26,6 +27,7 @@
 import androidx.compose.ui.layout.PlacementScopeMarker
 import androidx.compose.ui.platform.LocalGraphicsContext
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * Default camera distance for all layers
@@ -411,6 +413,8 @@
 
     internal var graphicsDensity: Density = Density(1.0f)
 
+    internal var layoutDirection: LayoutDirection = LayoutDirection.Ltr
+
     override val density: Float
         get() = graphicsDensity.density
 
@@ -425,6 +429,10 @@
             }
         }
 
+    internal var outline: Outline? = null
+        @VisibleForTesting
+        internal set
+
     fun reset() {
         scaleX = 1f
         scaleY = 1f
@@ -444,7 +452,12 @@
         renderEffect = null
         compositingStrategy = CompositingStrategy.Auto
         size = Size.Unspecified
+        outline = null
         // mutatedFields should be reset last as all the setters above modify it.
         mutatedFields = 0
     }
+
+    internal fun updateOutline() {
+        outline = shape.createOutline(size, layoutDirection, graphicsDensity)
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index 2e87f0b..8a6b586 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -517,18 +517,16 @@
             }
             graphicsLayerScope.reset()
             graphicsLayerScope.graphicsDensity = layoutNode.density
+            graphicsLayerScope.layoutDirection = layoutNode.layoutDirection
             graphicsLayerScope.size = size.toSize()
             snapshotObserver.observeReads(this, onCommitAffectingLayerParams) {
                 layerBlock.invoke(graphicsLayerScope)
+                graphicsLayerScope.updateOutline()
             }
             val layerPositionalProperties = layerPositionalProperties
                 ?: LayerPositionalProperties().also { layerPositionalProperties = it }
             layerPositionalProperties.copyFrom(graphicsLayerScope)
-            layer.updateLayerProperties(
-                graphicsLayerScope,
-                layoutNode.layoutDirection,
-                layoutNode.density,
-            )
+            layer.updateLayerProperties(graphicsLayerScope)
             isClipping = graphicsLayerScope.clip
             lastLayerAlpha = graphicsLayerScope.alpha
             if (invokeOnLayoutChange) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
index f68b387..602a375 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
@@ -22,10 +22,8 @@
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
 import androidx.compose.ui.graphics.layer.GraphicsLayer
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A layer returned by [Owner.createLayer] to separate drawn content.
@@ -33,13 +31,9 @@
 internal interface OwnedLayer {
 
     /**
-     * Applies the new layer properties and causing this layer to be redrawn.
+     * Applies the new layer properties, causing this layer to be redrawn.
      */
-    fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density,
-    )
+    fun updateLayerProperties(scope: ReusableGraphicsLayerScope)
 
     /**
      * Returns `false` if [position] is outside the clipped region or `true` if clipping
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
index 082fff8..adc95cf 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkiaLayerTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.DefaultShadowColor
@@ -351,7 +352,8 @@
 
         layer.resize(IntSize(1, 2))
         layer.updateProperties(
-            clip = true
+            clip = true,
+            size = Size(1f, 2f)
         )
 
         assertFalse(layer.isInLayer(Offset(-1f, -1f)))
@@ -363,7 +365,8 @@
         layer.resize(IntSize(100, 200))
         layer.updateProperties(
             clip = true,
-            shape = CircleShape
+            shape = CircleShape,
+            size = Size(100f, 200f)
         )
 
         assertFalse(layer.isInLayer(Offset(5f, 5f)))
@@ -394,7 +397,8 @@
         shape: Shape = RectangleShape,
         clip: Boolean = false,
         renderEffect: RenderEffect? = null,
-        compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
+        compositingStrategy: CompositingStrategy = CompositingStrategy.Auto,
+        size: Size = Size.Zero
     ) {
         val scope = ReusableGraphicsLayerScope()
         scope.cameraDistance = cameraDistance
@@ -415,6 +419,9 @@
         scope.clip = clip
         scope.renderEffect = renderEffect
         scope.compositingStrategy = compositingStrategy
-        updateLayerProperties(scope, LayoutDirection.Ltr, Density(1f))
+        scope.layoutDirection = LayoutDirection.Ltr
+        scope.graphicsDensity = Density(1f)
+        scope.outline = shape.createOutline(size, scope.layoutDirection, scope.graphicsDensity)
+        updateLayerProperties(scope)
     }
 }
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt
deleted file mode 100644
index 580fd2b..0000000
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/OutlineCache.skiko.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.platform
-
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-
-/**
- * Class for storing outline. Recalculates outline when [size] or [shape] is changed.
- * It' s needed so we don't have to recreate it every time we use it for rendering
- * (it can be expensive to create outline every frame).
- */
-internal class OutlineCache(
-    density: Density,
-    size: IntSize,
-    shape: Shape,
-    layoutDirection: LayoutDirection
-) {
-    var density = density
-        set(value) {
-            if (value != field) {
-                field = value
-                outline = createOutline()
-            }
-        }
-
-    var size = size
-        set(value) {
-            if (value != field) {
-                field = value
-                outline = createOutline()
-            }
-        }
-
-    var shape = shape
-        set(value) {
-            if (value != field) {
-                field = value
-                outline = createOutline()
-            }
-        }
-
-    var layoutDirection = layoutDirection
-        set(value) {
-            if (value != field) {
-                field = value
-                outline = createOutline()
-            }
-        }
-
-    var outline: Outline = createOutline()
-        private set
-
-    private fun createOutline() =
-        shape.createOutline(size.toSize(), layoutDirection, density)
-}
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
index 41bffb8..b39d3ea 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
@@ -31,7 +31,6 @@
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.RenderEffect
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
 import androidx.compose.ui.graphics.SkiaBackedCanvas
@@ -47,7 +46,6 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
 import org.jetbrains.skia.ClipMode
@@ -64,8 +62,7 @@
 ) : OwnedLayer {
     private var size = IntSize.Zero
     private var position = IntOffset.Zero
-    private var outlineCache =
-        OutlineCache(density, size, RectangleShape, LayoutDirection.Ltr)
+    private var outline: Outline? = null
     // Internal for testing
     internal val matrix = Matrix()
     private val pictureRecorder = PictureRecorder()
@@ -105,7 +102,6 @@
     override fun resize(size: IntSize) {
         if (size != this.size) {
             this.size = size
-            outlineCache.size = size
             updateMatrix()
             invalidate()
         }
@@ -133,11 +129,10 @@
 
         val x = position.x
         val y = position.y
-        if (outlineCache.shape === RectangleShape) {
-            return 0f <= x && x < size.width && 0f <= y && y < size.height
-        }
 
-        return isInOutline(outlineCache.outline, x, y)
+        val outline = outline ?: return true
+
+        return isInOutline(outline, x, y)
     }
 
     private fun getMatrix(inverse: Boolean): Matrix {
@@ -152,11 +147,7 @@
     }
     private var mutatedFields: Int = 0
 
-    override fun updateLayerProperties(
-        scope: ReusableGraphicsLayerScope,
-        layoutDirection: LayoutDirection,
-        density: Density,
-    ) {
+    override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) {
         val maybeChangedFields = scope.mutatedFields or mutatedFields
         this.transformOrigin = scope.transformOrigin
         this.translationX = scope.translationX
@@ -169,14 +160,12 @@
         this.alpha = scope.alpha
         this.clip = scope.clip
         this.shadowElevation = scope.shadowElevation
-        this.density = density
+        this.density = scope.graphicsDensity
         this.renderEffect = scope.renderEffect
         this.ambientShadowColor = scope.ambientShadowColor
         this.spotShadowColor = scope.spotShadowColor
         this.compositingStrategy = scope.compositingStrategy
-        outlineCache.shape = scope.shape
-        outlineCache.layoutDirection = layoutDirection
-        outlineCache.density = density
+        this.outline = scope.outline
         if (maybeChangedFields and Fields.MatrixAffectingFields != 0) {
             updateMatrix()
         }
@@ -245,13 +234,17 @@
                 drawShadow(canvas)
             }
 
-            if (clip) {
+            val outline = outline
+            val isClipping = if (clip && outline != null) {
                 canvas.save()
-                when (val outline = outlineCache.outline) {
+                when (outline) {
                     is Outline.Rectangle -> canvas.clipRect(outline.rect)
                     is Outline.Rounded -> canvas.clipRoundRect(outline.roundRect)
                     is Outline.Generic -> canvas.clipPath(outline.path)
                 }
+                true
+            } else {
+                false
             }
 
             val currentRenderEffect = renderEffect
@@ -279,7 +272,7 @@
 
             drawBlock(canvas, null)
             canvas.restore()
-            if (clip) {
+            if (isClipping) {
                 canvas.restore()
             }
         }
@@ -299,7 +292,7 @@
     override fun updateDisplayList() = Unit
 
     fun drawShadow(canvas: Canvas) = with(density) {
-        val path = when (val outline = outlineCache.outline) {
+        val path = when (val outline = outline) {
             is Outline.Rectangle -> Path().apply { addRect(outline.rect) }
             is Outline.Rounded -> Path().apply { addRoundRect(outline.roundRect) }
             is Outline.Generic -> outline.path
diff --git a/concurrent/concurrent-futures-ktx/src/main/resources/META-INF/NOTICE.txt b/concurrent/concurrent-futures-ktx/src/main/resources/META-INF/NOTICE.txt
new file mode 100644
index 0000000..ff3ce54
--- /dev/null
+++ b/concurrent/concurrent-futures-ktx/src/main/resources/META-INF/NOTICE.txt
@@ -0,0 +1,220 @@
+List of 3rd party licenses:
+-----------------------------------------------------------------------------
+* kotlinx.coroutines library.
+
+ ****** NOTICE:
+
+=========================================================================
+==  NOTICE file corresponding to the section 4 d of                    ==
+==  the Apache License, Version 2.0,                                   ==
+==  in this case for the kotlinx.coroutines library.                   ==
+=========================================================================
+
+kotlinx.coroutines library.
+Copyright 2016-2024 JetBrains s.r.o and contributors
+
+ ****** LICENSE:
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+
+   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.
+
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
index ef3d133..f7400fa 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
@@ -113,15 +113,23 @@
     @RequiresApi(30)
     @Deprecated
     private WindowInsetsControllerCompat(@NonNull WindowInsetsController insetsController) {
-        mImpl = new Impl30(insetsController,
-                this,
-                new SoftwareKeyboardControllerCompat(insetsController));
+        if (SDK_INT >= 35) {
+            mImpl = new Impl35(insetsController,
+                    this,
+                    new SoftwareKeyboardControllerCompat(insetsController));
+        } else {
+            mImpl = new Impl30(insetsController,
+                    this,
+                    new SoftwareKeyboardControllerCompat(insetsController));
+        }
     }
 
     public WindowInsetsControllerCompat(@NonNull Window window, @NonNull View view) {
         SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat =
                 new SoftwareKeyboardControllerCompat(view);
-        if (SDK_INT >= 30) {
+        if (SDK_INT >= 35) {
+            mImpl = new Impl35(window, this, softwareKeyboardControllerCompat);
+        } else if (SDK_INT >= 30) {
             mImpl = new Impl30(window, this, softwareKeyboardControllerCompat);
         } else if (SDK_INT >= 26) {
             mImpl = new Impl26(window, softwareKeyboardControllerCompat);
@@ -830,4 +838,33 @@
                             | systemUiFlag);
         }
     }
+
+    @RequiresApi(35)
+    private static class Impl35 extends Impl30 {
+
+        Impl35(@NonNull Window window,
+                @NonNull WindowInsetsControllerCompat compatController,
+                @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+            super(window, compatController, softwareKeyboardControllerCompat);
+        }
+
+        Impl35(@NonNull WindowInsetsController insetsController,
+                @NonNull WindowInsetsControllerCompat compatController,
+                @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+            super(insetsController, compatController, softwareKeyboardControllerCompat);
+        }
+
+        @Override
+        public boolean isAppearanceLightStatusBars() {
+            return (mInsetsController.getSystemBarsAppearance()
+                    & WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS) != 0;
+        }
+
+        @Override
+        public boolean isAppearanceLightNavigationBars() {
+            return (mInsetsController.getSystemBarsAppearance()
+                    & WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+        }
+
+    }
 }
diff --git a/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt b/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
index 89f79a8..1e8cb27 100644
--- a/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
+++ b/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
@@ -29,7 +29,6 @@
 import org.gradle.api.artifacts.result.ResolvedArtifactResult
 import org.gradle.api.attributes.Attribute
 import org.gradle.api.attributes.AttributeContainer
-import org.gradle.api.attributes.Bundling
 import org.gradle.api.attributes.Category
 import org.gradle.api.attributes.LibraryElements
 import org.gradle.api.attributes.Usage
@@ -317,7 +316,6 @@
                         attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, libraryElement)
                         attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME)
                         attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY)
-                        attribute(Bundling.BUNDLING_ATTRIBUTE, Bundling.EXTERNAL)
                         attribute(
                             TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
                             jvmEnvironment
@@ -339,7 +337,6 @@
                 createConfiguration(*dependencies) {
                     attributes.apply {
                         attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY)
-                        attribute(Bundling.BUNDLING_ATTRIBUTE, Bundling.EXTERNAL)
                         attribute(
                             TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
                             TargetJvmEnvironment.STANDARD_JVM
@@ -374,7 +371,6 @@
                         attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, libraryElement)
                         attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API)
                         attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY)
-                        attribute(Bundling.BUNDLING_ATTRIBUTE, Bundling.EXTERNAL)
                     }
                 }
             }
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index 770ad70..74b88cd 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -299,6 +299,11 @@
 
 #### Protobuf {#dependencies-protobuf}
 
+**Note**: It is preferred to use the [`wire`](https://github.com/square/wire)
+library for handling protocol buffers in Android libraries as it has a binary
+stable runtime. An example of its usage can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/build.gradle?q=wireRuntime%20file:gradle&ss=androidx%2Fplatform%2Fframeworks%2Fsupport).
+
 [Protocol buffers](https://developers.google.com/protocol-buffers) provide a
 language- and platform-neutral mechanism for serializing structured data. The
 implementation enables developers to maintain protocol compatibility across
@@ -306,8 +311,18 @@
 library versions included in their APKs.
 
 The Protobuf library itself, however, does not guarantee ABI compatibility
-across minor versions and a specific version **must** be bundled with a library
-to avoid conflict with other dependencies used by the developer.
+across minor versions and a specific version **must** be used with a library to
+avoid conflict with other dependencies used by the developer. To do this, you
+must first create a new project to repackage the protobuf runtime classes, and
+then have it as a dependency in the project you generate protos in. In the
+project that generates protos, you must also relocate any import statements
+containing `com.google.protobuf` to your target package name. The
+[AndroidXRepackagePlugin](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/private/src/main/kotlin/androidx/build/AndroidXRepackageImplPlugin.kt)
+abstracts this for you. An example of its use to repackage the protobuf runtime
+library can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/protolayout/protolayout-external-protobuf/build.gradle)
+and its associated use in the library that generates protos can be found
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/protolayout/protolayout-proto/build.gradle).
 
 Additionally, the Java API surface generated by the Protobuf compiler is not
 guaranteed to be stable and **must not** be exposed to developers. Library
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e34dcad..cd86e73 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -26,7 +26,7 @@
 autoValue = "1.6.3"
 binaryCompatibilityValidator = "0.15.0-Beta.2"
 byteBuddy = "1.14.9"
-asm = "9.3"
+asm = "9.7"
 cmake = "3.22.1"
 composeCompilerPlugin = "1.5.11"  # Update when pulling in new stable binaries
 dagger = "2.49"
diff --git a/graphics/graphics-path/build.gradle b/graphics/graphics-path/build.gradle
index 753e958..f24d85e 100644
--- a/graphics/graphics-path/build.gradle
+++ b/graphics/graphics-path/build.gradle
@@ -55,7 +55,6 @@
                     "-std=c++17",
                     "-Wno-unused-command-line-argument",
                     "-Wl,--hash-style=both", // Required to support API levels below 23
-                    "-fno-stack-protector",
                     "-fno-exceptions",
                     "-fno-unwind-tables",
                     "-fno-asynchronous-unwind-tables",
@@ -67,7 +66,6 @@
                     "-fomit-frame-pointer",
                     "-ffunction-sections",
                     "-fdata-sections",
-                    "-fstack-protector",
                     "-Wl,--gc-sections",
                     "-Wl,-Bsymbolic-functions",
                     "-nostdlib++"
diff --git a/graphics/graphics-shapes/api/1.0.0-beta02.txt b/graphics/graphics-shapes/api/1.0.0-beta02.txt
deleted file mode 100644
index f9d62d6..0000000
--- a/graphics/graphics-shapes/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,163 +0,0 @@
-// Signature format: 4.0
-package androidx.graphics.shapes {
-
-  public final class CornerRounding {
-    ctor public CornerRounding(optional @FloatRange(from=0.0) float radius, optional @FloatRange(from=0.0, to=1.0) float smoothing);
-    method public float getRadius();
-    method public float getSmoothing();
-    property public final float radius;
-    property public final float smoothing;
-    field public static final androidx.graphics.shapes.CornerRounding.Companion Companion;
-    field public static final androidx.graphics.shapes.CornerRounding Unrounded;
-  }
-
-  public static final class CornerRounding.Companion {
-  }
-
-  public class Cubic {
-    method public static final androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
-    method public final operator androidx.graphics.shapes.Cubic div(float x);
-    method public final operator androidx.graphics.shapes.Cubic div(int x);
-    method public final float getAnchor0X();
-    method public final float getAnchor0Y();
-    method public final float getAnchor1X();
-    method public final float getAnchor1Y();
-    method public final float getControl0X();
-    method public final float getControl0Y();
-    method public final float getControl1X();
-    method public final float getControl1Y();
-    method public final operator androidx.graphics.shapes.Cubic plus(androidx.graphics.shapes.Cubic o);
-    method public final androidx.graphics.shapes.Cubic reverse();
-    method public final kotlin.Pair<androidx.graphics.shapes.Cubic,androidx.graphics.shapes.Cubic> split(float t);
-    method public static final androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
-    method public final operator androidx.graphics.shapes.Cubic times(float x);
-    method public final operator androidx.graphics.shapes.Cubic times(int x);
-    method public final androidx.graphics.shapes.Cubic transformed(androidx.graphics.shapes.PointTransformer f);
-    property public final float anchor0X;
-    property public final float anchor0Y;
-    property public final float anchor1X;
-    property public final float anchor1Y;
-    property public final float control0X;
-    property public final float control0Y;
-    property public final float control1X;
-    property public final float control1Y;
-    field public static final androidx.graphics.shapes.Cubic.Companion Companion;
-  }
-
-  public static final class Cubic.Companion {
-    method public androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
-    method public androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
-  }
-
-  public final class CubicKt {
-    method public static androidx.graphics.shapes.Cubic Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
-  }
-
-  public final class Morph {
-    ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
-    method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
-    method public float[] calculateBounds();
-    method public float[] calculateBounds(optional float[] bounds);
-    method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
-    method public float[] calculateMaxBounds(optional float[] bounds);
-    method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
-    method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
-  }
-
-  public final class MutableCubic extends androidx.graphics.shapes.Cubic {
-    ctor public MutableCubic();
-    method public void interpolate(androidx.graphics.shapes.Cubic c1, androidx.graphics.shapes.Cubic c2, float progress);
-    method public void transform(androidx.graphics.shapes.PointTransformer f);
-  }
-
-  public interface MutablePoint {
-    method public float getX();
-    method public float getY();
-    method public void setX(float);
-    method public void setY(float);
-    property public abstract float x;
-    property public abstract float y;
-  }
-
-  public fun interface PointTransformer {
-    method public long transform(float x, float y);
-  }
-
-  public final class RoundedPolygon {
-    method public float[] calculateBounds();
-    method public float[] calculateBounds(optional float[] bounds);
-    method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
-    method public float[] calculateMaxBounds(optional float[] bounds);
-    method public float getCenterX();
-    method public float getCenterY();
-    method public java.util.List<androidx.graphics.shapes.Cubic> getCubics();
-    method public androidx.graphics.shapes.RoundedPolygon normalized();
-    method public androidx.graphics.shapes.RoundedPolygon transformed(androidx.graphics.shapes.PointTransformer f);
-    property public final float centerX;
-    property public final float centerY;
-    property public final java.util.List<androidx.graphics.shapes.Cubic> cubics;
-    field public static final androidx.graphics.shapes.RoundedPolygon.Companion Companion;
-  }
-
-  public static final class RoundedPolygon.Companion {
-  }
-
-  public final class RoundedPolygonKt {
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(androidx.graphics.shapes.RoundedPolygon source);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-  }
-
-  public final class ShapesKt {
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-  }
-
-  public final class Shapes_androidKt {
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.Morph, float progress, optional android.graphics.Path path);
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon);
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon, optional android.graphics.Path path);
-    method public static androidx.graphics.shapes.RoundedPolygon transformed(androidx.graphics.shapes.RoundedPolygon, android.graphics.Matrix matrix);
-  }
-
-}
-
diff --git a/graphics/graphics-shapes/api/restricted_1.0.0-beta02.txt b/graphics/graphics-shapes/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index d691f29..0000000
--- a/graphics/graphics-shapes/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,164 +0,0 @@
-// Signature format: 4.0
-package androidx.graphics.shapes {
-
-  public final class CornerRounding {
-    ctor public CornerRounding(optional @FloatRange(from=0.0) float radius, optional @FloatRange(from=0.0, to=1.0) float smoothing);
-    method public float getRadius();
-    method public float getSmoothing();
-    property public final float radius;
-    property public final float smoothing;
-    field public static final androidx.graphics.shapes.CornerRounding.Companion Companion;
-    field public static final androidx.graphics.shapes.CornerRounding Unrounded;
-  }
-
-  public static final class CornerRounding.Companion {
-  }
-
-  public class Cubic {
-    method public static final androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
-    method public final operator androidx.graphics.shapes.Cubic div(float x);
-    method public final operator androidx.graphics.shapes.Cubic div(int x);
-    method public final float getAnchor0X();
-    method public final float getAnchor0Y();
-    method public final float getAnchor1X();
-    method public final float getAnchor1Y();
-    method public final float getControl0X();
-    method public final float getControl0Y();
-    method public final float getControl1X();
-    method public final float getControl1Y();
-    method public final operator androidx.graphics.shapes.Cubic plus(androidx.graphics.shapes.Cubic o);
-    method public final androidx.graphics.shapes.Cubic reverse();
-    method public final kotlin.Pair<androidx.graphics.shapes.Cubic,androidx.graphics.shapes.Cubic> split(float t);
-    method public static final androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
-    method public final operator androidx.graphics.shapes.Cubic times(float x);
-    method public final operator androidx.graphics.shapes.Cubic times(int x);
-    method public final androidx.graphics.shapes.Cubic transformed(androidx.graphics.shapes.PointTransformer f);
-    property public final float anchor0X;
-    property public final float anchor0Y;
-    property public final float anchor1X;
-    property public final float anchor1Y;
-    property public final float control0X;
-    property public final float control0Y;
-    property public final float control1X;
-    property public final float control1Y;
-    field public static final androidx.graphics.shapes.Cubic.Companion Companion;
-  }
-
-  public static final class Cubic.Companion {
-    method public androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
-    method public androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
-  }
-
-  public final class CubicKt {
-    method public static androidx.graphics.shapes.Cubic Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
-  }
-
-  public final class Morph {
-    ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
-    method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
-    method public float[] calculateBounds();
-    method public float[] calculateBounds(optional float[] bounds);
-    method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
-    method public float[] calculateMaxBounds(optional float[] bounds);
-    method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
-    method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
-    property @kotlin.PublishedApi internal final java.util.List<kotlin.Pair<androidx.graphics.shapes.Cubic,androidx.graphics.shapes.Cubic>> morphMatch;
-  }
-
-  public final class MutableCubic extends androidx.graphics.shapes.Cubic {
-    ctor public MutableCubic();
-    method public void interpolate(androidx.graphics.shapes.Cubic c1, androidx.graphics.shapes.Cubic c2, float progress);
-    method public void transform(androidx.graphics.shapes.PointTransformer f);
-  }
-
-  public interface MutablePoint {
-    method public float getX();
-    method public float getY();
-    method public void setX(float);
-    method public void setY(float);
-    property public abstract float x;
-    property public abstract float y;
-  }
-
-  public fun interface PointTransformer {
-    method public long transform(float x, float y);
-  }
-
-  public final class RoundedPolygon {
-    method public float[] calculateBounds();
-    method public float[] calculateBounds(optional float[] bounds);
-    method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
-    method public float[] calculateMaxBounds(optional float[] bounds);
-    method public float getCenterX();
-    method public float getCenterY();
-    method public java.util.List<androidx.graphics.shapes.Cubic> getCubics();
-    method public androidx.graphics.shapes.RoundedPolygon normalized();
-    method public androidx.graphics.shapes.RoundedPolygon transformed(androidx.graphics.shapes.PointTransformer f);
-    property public final float centerX;
-    property public final float centerY;
-    property public final java.util.List<androidx.graphics.shapes.Cubic> cubics;
-    field public static final androidx.graphics.shapes.RoundedPolygon.Companion Companion;
-  }
-
-  public static final class RoundedPolygon.Companion {
-  }
-
-  public final class RoundedPolygonKt {
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(androidx.graphics.shapes.RoundedPolygon source);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(float[] vertices, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon RoundedPolygon(@IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-  }
-
-  public final class ShapesKt {
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon circle(androidx.graphics.shapes.RoundedPolygon.Companion, optional @IntRange(from=3L) int numVertices, optional float radius, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon pill(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional float smoothing, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX);
-    method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius, optional float innerRadius, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
-  }
-
-  public final class Shapes_androidKt {
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.Morph, float progress, optional android.graphics.Path path);
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon);
-    method public static android.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon, optional android.graphics.Path path);
-    method public static androidx.graphics.shapes.RoundedPolygon transformed(androidx.graphics.shapes.RoundedPolygon, android.graphics.Matrix matrix);
-  }
-
-}
-
diff --git a/input/input-motionprediction/api/1.0.0-beta04.txt b/input/input-motionprediction/api/1.0.0-beta04.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/1.0.0-beta04.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/graphics/graphics-shapes/api/res-1.0.0-beta02.txt b/input/input-motionprediction/api/res-1.0.0-beta04.txt
similarity index 100%
rename from graphics/graphics-shapes/api/res-1.0.0-beta02.txt
rename to input/input-motionprediction/api/res-1.0.0-beta04.txt
diff --git a/input/input-motionprediction/api/restricted_1.0.0-beta04.txt b/input/input-motionprediction/api/restricted_1.0.0-beta04.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/restricted_1.0.0-beta04.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
index 65a95d3..d5a7f7d 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
@@ -176,7 +176,11 @@
 fun packageInspector(libraryProject: Project, inspectorProjectPath: String) {
     val inspectorProject = libraryProject.rootProject.findProject(inspectorProjectPath)
     if (inspectorProject == null) {
-        check(libraryProject.property("androidx.studio.type") == "playground") {
+        val extraProperties = libraryProject.extensions.extraProperties
+        check(
+            extraProperties.has("androidx.studio.type") &&
+            extraProperties.get("androidx.studio.type") == "playground"
+        ) {
             "Cannot find $inspectorProjectPath. This is optional only for playground builds."
         }
         // skip setting up inspector project
diff --git a/libraryversions.toml b/libraryversions.toml
index 722925b2..138ac26 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -72,7 +72,7 @@
 GRAPHICS_CORE = "1.0.0-rc01"
 GRAPHICS_FILTERS = "1.0.0-alpha01"
 GRAPHICS_PATH = "1.0.0-rc01"
-GRAPHICS_SHAPES = "1.0.0-beta02"
+GRAPHICS_SHAPES = "1.0.0-beta01"
 GRIDLAYOUT = "1.1.0-beta02"
 HEALTH_CONNECT = "1.1.0-alpha08"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha03"
@@ -80,7 +80,7 @@
 HILT = "1.2.0-rc01"
 HILT_NAVIGATION = "1.2.0-rc01"
 HILT_NAVIGATION_COMPOSE = "1.2.0-rc01"
-INPUT_MOTIONPREDICTION = "1.0.0-beta03"
+INPUT_MOTIONPREDICTION = "1.0.0-beta04"
 INSPECTION = "1.0.0"
 INTERPOLATOR = "1.1.0-alpha01"
 JAVASCRIPTENGINE = "1.0.0-beta01"
diff --git a/lifecycle/lifecycle-compiler/src/main/resources/NOTICE.txt b/lifecycle/lifecycle-compiler/src/main/resources/META-INF/NOTICE.txt
similarity index 100%
rename from lifecycle/lifecycle-compiler/src/main/resources/NOTICE.txt
rename to lifecycle/lifecycle-compiler/src/main/resources/META-INF/NOTICE.txt
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/DiscouragedGradleMethodDetector.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/DiscouragedGradleMethodDetector.kt
index a8f7b18..b944fbb 100644
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/DiscouragedGradleMethodDetector.kt
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/DiscouragedGradleMethodDetector.kt
@@ -131,6 +131,8 @@
             "findByPath" to Replacement(TASK_CONTAINER, null, EAGER_CONFIGURATION_ISSUE),
             "findProperty" to
                 Replacement(PROJECT, "providers.gradleProperty", PROJECT_ISOLATION_ISSUE),
+            "property" to
+                Replacement(PROJECT, "providers.gradleProperty", PROJECT_ISOLATION_ISSUE),
             "iterator" to Replacement(TASK_CONTAINER, null, EAGER_CONFIGURATION_ISSUE),
             "get" to Replacement(TASK_PROVIDER, null, EAGER_CONFIGURATION_ISSUE),
             "getAt" to Replacement(TASK_COLLECTION, "named", EAGER_CONFIGURATION_ISSUE),
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index 8981c6a..d2a4ef6 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -77,7 +77,7 @@
             }
         }
     ) { innerPadding ->
-        NavHost(navController, Profile::class, Modifier.padding(innerPadding)) {
+        NavHost(navController, Profile, Modifier.padding(innerPadding)) {
             composable<Profile> { Profile(navController) }
             composable<Dashboard> { Dashboard(navController) }
             composable<Scrollable> { Scrollable(navController) }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
index 97a956b..289eab6 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
@@ -28,7 +28,7 @@
 @Composable
 fun NavWithArgsDemo() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Profile::class) {
+    NavHost(navController, startDestination = Profile) {
         composable<Profile> { ProfileWithArgs(navController) }
         composable<Dashboard> { backStackEntry ->
             val dashboard = backStackEntry.toRoute<Dashboard>()
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index b856932..02acffa 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -92,7 +92,7 @@
 @Composable
 fun BasicNav() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Profile::class) {
+    NavHost(navController, startDestination = Profile) {
         composable<Profile> { Profile(navController) }
         composable<Dashboard>(
             enterTransition = {
@@ -141,8 +141,8 @@
 @Composable
 fun NestedNavStartDestination() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Nested::class) {
-        navigation<Nested>(startDestination = Profile::class) {
+    NavHost(navController, startDestination = Nested) {
+        navigation<Nested>(startDestination = Profile) {
             composable<Profile> { Profile(navController) }
         }
         composable<Dashboard> { Dashboard(navController) }
@@ -154,9 +154,9 @@
 @Composable
 fun NestedNavInGraph() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Profile::class) {
+    NavHost(navController, startDestination = Profile) {
         composable<Profile> { Profile(navController) }
-        navigation<Dashboard>(startDestination = Nested::class) {
+        navigation<Dashboard>(startDestination = Nested) {
             composable<Nested> { Dashboard(navController) }
         }
         composable<Scrollable> { Scrollable(navController) }
@@ -169,7 +169,7 @@
 fun NavScaffold() {
     val navController = rememberNavController()
     Scaffold { innerPadding ->
-        NavHost(navController, Profile::class, Modifier.padding(innerPadding)) {
+        NavHost(navController, Profile, Modifier.padding(innerPadding)) {
             composable<Profile> { Profile(navController) }
             composable<Dashboard> { Dashboard(navController) }
             composable<Scrollable> { Scrollable(navController) }
@@ -182,7 +182,7 @@
 @Composable
 fun NavWithArgsInNestedGraph() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Profile::class) {
+    NavHost(navController, startDestination = Profile) {
         composable<Profile> { ProfileWithArgs(navController) }
         navigation<Dashboard>(startDestination = NestedWithArg::class) {
             composable<NestedWithArg> {
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
index 7a8b34a..777c33f 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
@@ -50,8 +50,6 @@
                 classpath = extraClasspath,
                 symbolProcessorProviders = symbolProcessorProviders,
                 processorOptions = processorOptions,
-                // b/328813158 to remove targeting of Java 17
-                javacArguments = listOf("-source", "17", "-target", "17")
             )
         )
     }
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
index 2513b5d..f253f94 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
@@ -16,11 +16,20 @@
 
 package androidx.privacysandbox.ui.integration.testapp
 
+import android.app.Activity
 import android.os.Bundle
+import android.util.Log
+import android.view.ViewGroup
+import android.widget.TextView
+import android.widget.Toast
 import androidx.fragment.app.Fragment
 import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat
+import androidx.privacysandbox.sdkruntime.client.SdkSandboxProcessDeathCallbackCompat
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
 import androidx.privacysandbox.ui.client.view.SandboxedSdkView
 import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+import kotlinx.coroutines.runBlocking
 
 /**
  * Base fragment to be used for testing different manual flows.
@@ -31,16 +40,22 @@
  */
 abstract class BaseFragment : Fragment() {
     private lateinit var sdkApi: ISdkApi
+    private lateinit var sdkSandboxManager: SdkSandboxManagerCompat
+    private lateinit var activity: Activity
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        val sdkSandboxManager = SdkSandboxManagerCompat.from(requireContext())
-        val loadedSdks = sdkSandboxManager.getSandboxedSdks()
-        val loadedSdk = loadedSdks.firstOrNull { it.getSdkInfo()?.name == SDK_NAME }
-        if (loadedSdk == null) {
-            throw IllegalStateException("SDK not loaded")
+        activity = requireActivity()
+        sdkSandboxManager = SdkSandboxManagerCompat.from(requireContext())
+        runBlocking {
+            val loadedSdks = sdkSandboxManager.getSandboxedSdks()
+            var loadedSdk = loadedSdks.firstOrNull { it.getSdkInfo()?.name == SDK_NAME }
+            if (loadedSdk == null) {
+                loadedSdk = sdkSandboxManager.loadSdk(SDK_NAME, Bundle())
+                sdkSandboxManager.loadSdk(MEDIATEE_SDK_NAME, Bundle())
+            }
+            sdkApi = ISdkApi.Stub.asInterface(loadedSdk.getInterface())
         }
-        sdkApi = ISdkApi.Stub.asInterface(loadedSdk.getInterface())
     }
 
     /**
@@ -50,6 +65,20 @@
         return sdkApi
     }
 
+    fun SandboxedSdkView.addStateChangedListener() {
+        addStateChangedListener(StateChangeListener(this))
+    }
+
+    /**
+     * Unloads all SDKs, resulting in sandbox death. This method registers a death callback to
+     * ensure that the app is not also killed.
+     */
+    fun unloadAllSdks() {
+        sdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, DeathCallbackImpl())
+        sdkSandboxManager.unloadSdk(SDK_NAME)
+        sdkSandboxManager.unloadSdk(MEDIATEE_SDK_NAME)
+    }
+
     /**
      * Called when the app's drawer layout state changes. When called, change the Z-order of
      * any [SandboxedSdkView] owned by the fragment to ensure that the remote UI is not drawn over
@@ -58,8 +87,37 @@
      */
     abstract fun handleDrawerStateChange(isDrawerOpen: Boolean)
 
+    private inner class StateChangeListener(val view: SandboxedSdkView) :
+        SandboxedSdkUiSessionStateChangedListener {
+        override fun onStateChanged(state: SandboxedSdkUiSessionState) {
+            Log.i(TAG, "UI session state changed to: $state")
+            if (state is SandboxedSdkUiSessionState.Error) {
+                // If the session fails to open, display the error.
+                val parent = view.parent as ViewGroup
+                val index = parent.indexOfChild(view)
+                val textView = TextView(requireActivity())
+                textView.text = state.throwable.message
+
+                requireActivity().runOnUiThread {
+                    parent.removeView(view)
+                    parent.addView(textView, index)
+                }
+            }
+        }
+    }
+
+    private inner class DeathCallbackImpl : SdkSandboxProcessDeathCallbackCompat {
+        override fun onSdkSandboxDied() {
+            activity.runOnUiThread {
+                Toast.makeText(activity, "Sandbox died", Toast.LENGTH_LONG).show()
+            }
+        }
+    }
+
     companion object {
         private const val SDK_NAME = "androidx.privacysandbox.ui.integration.testsdkprovider"
+        private const val MEDIATEE_SDK_NAME =
+            "androidx.privacysandbox.ui.integration.mediateesdkprovider"
         const val TAG = "TestSandboxClient"
     }
 }
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt
deleted file mode 100644
index 3a27fde..0000000
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/EmptyFragment.kt
+++ /dev/null
@@ -1,22 +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.privacysandbox.ui.integration.testapp
-
-class EmptyFragment : BaseFragment() {
-    override fun handleDrawerStateChange(isDrawerOpen: Boolean) {
-    }
-}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index ae13cb1..8d7af17 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -109,7 +109,7 @@
             val itemId = it.itemId
             when (itemId) {
                 R.id.item_main -> switchContentFragment(MainFragment(), it.title)
-                R.id.item_empty -> switchContentFragment(EmptyFragment(), it.title)
+                R.id.item_sandbox_death -> switchContentFragment(SandboxDeathFragment(), it.title)
                 else -> {
                     Log.e(TAG, "Invalid fragment option")
                     true
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
index 7944323..e9b2ccc 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainFragment.kt
@@ -17,16 +17,12 @@
 package androidx.privacysandbox.ui.integration.testapp
 
 import android.os.Bundle
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
 import android.widget.LinearLayout
-import android.widget.TextView
 import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
-import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState
-import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
 import androidx.privacysandbox.ui.client.view.SandboxedSdkView
 import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
 import com.google.android.material.switchmaterial.SwitchMaterial
@@ -79,7 +75,7 @@
     }
 
     private fun loadWebViewBannerAd() {
-        webViewBannerView.addStateChangedListener(StateChangeListener(webViewBannerView))
+        webViewBannerView.addStateChangedListener()
         webViewBannerView.setAdapter(
             SandboxedUiAdapterFactory.createFromCoreLibInfo(
             sdkApi.loadLocalWebViewAd()
@@ -101,7 +97,7 @@
     }
 
     private fun loadBottomBannerAd() {
-        bottomBannerView.addStateChangedListener(StateChangeListener(bottomBannerView))
+        bottomBannerView.addStateChangedListener()
         bottomBannerView.layoutParams = inflatedView.findViewById<LinearLayout>(
             R.id.bottom_banner_container).layoutParams
         requireActivity().runOnUiThread {
@@ -115,8 +111,7 @@
     }
 
     private fun loadResizableBannerAd() {
-        resizableBannerView.addStateChangedListener(
-            StateChangeListener(resizableBannerView))
+        resizableBannerView.addStateChangedListener()
         resizableBannerView.setAdapter(
             SandboxedUiAdapterFactory.createFromCoreLibInfo(
             sdkApi.loadTestAdWithWaitInsideOnDraw(/*text=*/ "Resizable View")
@@ -164,23 +159,4 @@
             sdkApi.requestResize(newWidth, newHeight)
         }
     }
-
-    private inner class StateChangeListener(val view: SandboxedSdkView) :
-        SandboxedSdkUiSessionStateChangedListener {
-        override fun onStateChanged(state: SandboxedSdkUiSessionState) {
-            Log.i(TAG, "UI session state changed to: $state")
-            if (state is SandboxedSdkUiSessionState.Error) {
-                // If the session fails to open, display the error.
-                val parent = view.parent as ViewGroup
-                val index = parent.indexOfChild(view)
-                val textView = TextView(requireActivity())
-                textView.text = state.throwable.message
-
-                requireActivity().runOnUiThread {
-                    parent.removeView(view)
-                    parent.addView(textView, index)
-                }
-            }
-        }
-    }
 }
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt
new file mode 100644
index 0000000..2c85ccd
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/SandboxDeathFragment.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.privacysandbox.ui.integration.testapp
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
+import androidx.privacysandbox.ui.client.view.SandboxedSdkView
+import androidx.privacysandbox.ui.integration.testaidl.ISdkApi
+
+class SandboxDeathFragment : BaseFragment() {
+    private lateinit var sdkApi: ISdkApi
+    private lateinit var inflatedView: View
+    private lateinit var sandboxedSdkView: SandboxedSdkView
+
+    override fun handleDrawerStateChange(isDrawerOpen: Boolean) {
+        sandboxedSdkView.orderProviderUiAboveClientUi(!isDrawerOpen)
+    }
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        inflatedView = inflater.inflate(R.layout.fragment_sandbox_death, container, false)
+        sdkApi = getSdkApi()
+        onLoaded()
+        return inflatedView
+    }
+
+    private fun onLoaded() {
+        sandboxedSdkView = inflatedView.findViewById(R.id.remote_view)
+        sandboxedSdkView.addStateChangedListener()
+        sandboxedSdkView.setAdapter(
+            SandboxedUiAdapterFactory.createFromCoreLibInfo(sdkApi.loadTestAd("Test Ad")))
+        val unloadSdksButton: Button = inflatedView.findViewById(R.id.unload_all_sdks_button)
+        unloadSdksButton.setOnClickListener {
+            unloadAllSdks()
+        }
+    }
+}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
index 836375e..1cde836 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/action_menu.xml
@@ -19,6 +19,6 @@
         android:id="@+id/item_main"
         android:title="Main CUJ" />
     <item
-        android:id="@+id/item_empty"
-        android:title="Empty CUJ" />
+        android:id="@+id/item_sandbox_death"
+        android:title="Sandbox Death CUJ" />
 </menu>
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml
new file mode 100644
index 0000000..02015bb
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_sandbox_death.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <androidx.privacysandbox.ui.client.view.SandboxedSdkView
+        android:layout_width="500dp"
+        android:layout_height="500dp"
+        android:id="@+id/remote_view"/>
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:id="@+id/unload_all_sdks_button"
+        android:text="@string/unload_sdks_button"/>
+</androidx.appcompat.widget.LinearLayoutCompat>
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
index 5ecbc59..33f5242 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
@@ -21,4 +21,5 @@
     <string name="local_to_internet_switch">local webview</string>
     <string name="mediation_switch">Mediation</string>
     <string name="app_owned_mediatee_switch">AppOwnedMediatee</string>
+    <string name="unload_sdks_button">Unload SDKs</string>
 </resources>
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index a94fd8f..d25dc59 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -23,6 +23,7 @@
 import android.os.Build
 import android.os.Bundle
 import android.os.IBinder
+import android.os.RemoteException
 import android.util.Log
 import android.view.Display
 import android.view.SurfaceControlViewHost
@@ -233,14 +234,16 @@
                 context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
             val displayId = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).displayId
 
-            adapterInterface.openRemoteSession(
-                windowInputToken,
-                displayId,
-                initialWidth,
-                initialHeight,
-                isZOrderOnTop,
-                RemoteSessionClient(context, client, clientExecutor)
-            )
+            tryToCallRemoteObject {
+                adapterInterface.openRemoteSession(
+                    windowInputToken,
+                    displayId,
+                    initialWidth,
+                    initialHeight,
+                    isZOrderOnTop,
+                    RemoteSessionClient(context, client, clientExecutor)
+                )
+            }
         }
 
         class RemoteSessionClient(
@@ -263,6 +266,11 @@
                         .onSessionOpened(SessionImpl(surfaceView,
                             remoteSessionController, surfacePackage))
                 }
+                tryToCallRemoteObject {
+                    remoteSessionController.asBinder().linkToDeath({
+                        onRemoteSessionError("Remote process died")
+                    }, 0)
+                }
             }
 
             override fun onRemoteSessionError(errorString: String) {
@@ -287,7 +295,9 @@
             override val view: View = surfaceView
 
             override fun notifyConfigurationChanged(configuration: Configuration) {
-                remoteSessionController.notifyConfigurationChanged(configuration)
+                tryToCallRemoteObject {
+                    remoteSessionController.notifyConfigurationChanged(configuration)
+                }
             }
 
             @SuppressLint("ClassVerificationFailure")
@@ -302,7 +312,9 @@
                 }
 
                 val providerResizeRunnable = Runnable {
-                    remoteSessionController.notifyResized(width, height)
+                    tryToCallRemoteObject {
+                        remoteSessionController.notifyResized(width, height)
+                    }
                 }
 
                 val syncGroup = SurfaceSyncGroup("AppAndSdkViewsSurfaceSync")
@@ -314,11 +326,27 @@
 
             override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
                 surfaceView.setZOrderOnTop(isZOrderOnTop)
-                remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
+                tryToCallRemoteObject {
+                    remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
+                }
             }
 
             override fun close() {
-                remoteSessionController.close()
+                 tryToCallRemoteObject { remoteSessionController.close() }
+            }
+        }
+
+        private companion object {
+
+            /**
+             * Tries to call the remote object and handles exceptions if the remote object has died.
+             */
+            private inline fun tryToCallRemoteObject(function: () -> Unit) {
+                try {
+                    function()
+                } catch (e: RemoteException) {
+                    Log.e(TAG, "Calling remote object failed: $e")
+                }
             }
         }
     }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
index 1eb28cf..0166c7d 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
@@ -416,4 +416,57 @@
         assertContentEquals(arrayOf(1, 2), resultArrayWithLong)
         assertContentEquals(longArrayOf(1, 2), resultLongArray)
     }
+
+    @Test
+    fun relation1to1() = runTest {
+        val sampleEntity1 = SampleEntity(1, 1)
+        val sampleEntity2 = SampleEntity2(1, 2)
+        db.dao().insert(sampleEntity1)
+        db.dao().insert(sampleEntity2)
+        assertThat(
+            db.dao().getSample1To2()
+        ).isEqualTo(
+            SampleDao.Sample1And2(sample1 = sampleEntity1, sample2 = sampleEntity2)
+        )
+    }
+
+    @Test
+    fun relation1toMany() = runTest {
+        val sampleEntity1 = SampleEntity(1, 1)
+        val sampleEntity2 = SampleEntity2(1, 2)
+        val sampleEntity2s = listOf(sampleEntity2, SampleEntity2(2, 3))
+
+        db.dao().insert(sampleEntity1)
+        db.dao().insertSampleEntity2List(sampleEntity2s)
+
+        assertThat(
+            db.dao().getSample1ToMany()
+        ).isEqualTo(
+            SampleDao.Sample1AndMany(
+                sample1 = sampleEntity1,
+                sample2s = listOf(sampleEntity2)
+            )
+        )
+    }
+
+    @Test
+    fun relationManytoMany() = runTest {
+        val sampleEntity1 = SampleEntity(1, 1)
+        val sampleEntity1s = listOf(sampleEntity1, SampleEntity(2, 2))
+
+        val sampleEntity2 = SampleEntity2(1, 1)
+        val sampleEntity2s = listOf(sampleEntity2, SampleEntity2(2, 2))
+
+        db.dao().insertSampleEntityList(sampleEntity1s)
+        db.dao().insertSampleEntity2List(sampleEntity2s)
+
+        assertThat(
+            db.dao().getSampleManyToMany()
+        ).isEqualTo(
+            SampleDao.SampleManyAndMany(
+                sample1 = sampleEntity1,
+                sample2s = listOf()
+            )
+        )
+    }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index 8787aed..f41e88e 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -20,12 +20,16 @@
 import androidx.room.Dao
 import androidx.room.Database
 import androidx.room.Delete
+import androidx.room.Embedded
 import androidx.room.Entity
 import androidx.room.ForeignKey
+import androidx.room.Index
 import androidx.room.Insert
+import androidx.room.Junction
 import androidx.room.MapColumn
 import androidx.room.PrimaryKey
 import androidx.room.Query
+import androidx.room.Relation
 import androidx.room.RewriteQueriesToDropUnusedColumns
 import androidx.room.RoomDatabase
 import androidx.room.Transaction
@@ -71,6 +75,15 @@
     val dataCopy: Long
 )
 
+@Entity(
+    primaryKeys = ["sample1Key", "sample2Key"],
+    indices = [Index("sample1Key"), Index("sample2Key")]
+)
+data class Sample1Sample2XRef(
+    val sample1Key: Long,
+    val sample2Key: Long,
+)
+
 @Dao
 interface SampleDao {
 
@@ -142,6 +155,12 @@
     suspend fun insertArray(entities: Array<SampleEntity>)
 
     @Insert
+    suspend fun insertSampleEntityList(entities: List<SampleEntity>)
+
+    @Insert
+    suspend fun insertSampleEntity2List(entities: List<SampleEntity2>)
+
+    @Insert
     suspend fun insert(entity: SampleEntity2)
 
     @Insert
@@ -167,6 +186,47 @@
 
     @Query("SELECT pk FROM SampleEntity")
     suspend fun queryOfLongArray(): LongArray
+
+    @Transaction
+    @Query("SELECT * FROM SampleEntity")
+    suspend fun getSample1To2(): Sample1And2
+
+    @Transaction
+    @Query("SELECT * FROM SampleEntity")
+    suspend fun getSample1ToMany(): Sample1AndMany
+
+    @Transaction
+    @Query("SELECT * FROM SampleEntity")
+    suspend fun getSampleManyToMany(): SampleManyAndMany
+
+    data class Sample1And2(
+        @Embedded
+        val sample1: SampleEntity,
+        @Relation(parentColumn = "pk", entityColumn = "pk2")
+        val sample2: SampleEntity2
+    )
+
+    data class Sample1AndMany(
+        @Embedded
+        val sample1: SampleEntity,
+        @Relation(parentColumn = "pk", entityColumn = "pk2")
+        val sample2s: List<SampleEntity2>
+    )
+
+    data class SampleManyAndMany(
+        @Embedded
+        val sample1: SampleEntity,
+        @Relation(
+            parentColumn = "pk",
+            entityColumn = "pk2",
+            associateBy = Junction(
+                value = Sample1Sample2XRef::class,
+                parentColumn = "sample1Key",
+                entityColumn = "sample2Key"
+            )
+        )
+        val sample2s: List<SampleEntity2>
+    )
 }
 
 @Database(
@@ -174,7 +234,8 @@
         SampleEntity::class,
         SampleEntity2::class,
         SampleEntity3::class,
-        SampleEntityCopy::class],
+        SampleEntityCopy::class,
+        Sample1Sample2XRef::class],
     version = 1,
     exportSchema = false
 )
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 42a9e97..2bc4be2 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -81,7 +81,6 @@
     shadowed(libs.kotlinMetadataJvm) {
         exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
     }
-    implementation(libs.intellijAnnotations)
     implementation(libs.kspApi)
     implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index 0171412..5296e39 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -148,6 +148,15 @@
             kotlin = com.squareup.kotlinpoet.ANY
         )
 
+        /**
+         * A convenience [XTypeName] that represents [kotlin.Enum] in Kotlin and
+         * [java.lang.Enum] in Java.
+         */
+        val ENUM = XTypeName(
+            java = JClassName.get(java.lang.Enum::class.java),
+            kotlin = com.squareup.kotlinpoet.ENUM
+        )
+
         val PRIMITIVE_BOOLEAN = Boolean::class.asPrimitiveTypeName()
         val PRIMITIVE_BYTE = Byte::class.asPrimitiveTypeName()
         val PRIMITIVE_SHORT = Short::class.asPrimitiveTypeName()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
index bf9c0d6..7a80d08 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
@@ -26,6 +26,7 @@
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Nullability
 import com.google.devtools.ksp.symbol.Variance
 import com.squareup.kotlinpoet.ANY
 import com.squareup.kotlinpoet.KModifier
@@ -92,8 +93,16 @@
     val mutableBounds = mutableListOf(ANY.copy(nullable = true))
     val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
     typeArgumentTypeLookup[name] = typeName
-    val resolvedBounds = bounds.map {
-        it.asKTypeName(resolver, typeArgumentTypeLookup)
+    val resolvedBounds = bounds.map { typeReference ->
+        typeReference.asKTypeName(resolver, typeArgumentTypeLookup).let { kTypeName ->
+            typeReference.resolve().let {
+                if (it.nullability == Nullability.PLATFORM) {
+                    kTypeName.copy(nullable = true)
+                } else {
+                    kTypeName
+                }
+            }
+        }
     }.toList()
     if (resolvedBounds.isNotEmpty()) {
         mutableBounds.addAll(resolvedBounds)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt
index 5a9d520..748503b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt
@@ -25,6 +25,7 @@
 import androidx.room.compiler.processing.XTypeVariableType
 import com.google.devtools.ksp.symbol.KSAnnotation
 import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.Nullability
 import com.squareup.javapoet.TypeName
 import kotlin.reflect.KClass
 
@@ -53,7 +54,16 @@
         )
     }
 
-    override val upperBounds: List<XType> = ksTypeVariable.bounds.map(env::wrap).toList()
+    override val upperBounds: List<XType> = ksTypeVariable.bounds.map {
+        val type = it.resolve().let {
+            if (it.nullability == Nullability.PLATFORM) {
+                it.withNullability(XNullability.NULLABLE)
+            } else {
+                it
+            }
+        }
+        env.wrap(it, type)
+    }.toList()
 
     override fun annotations(): Sequence<KSAnnotation> {
         return ksTypeVariable.annotations
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeParameterElement.kt
index 64e518b..26fa62a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeParameterElement.kt
@@ -18,10 +18,12 @@
 
 import androidx.room.compiler.processing.XAnnotated
 import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeParameterElement
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_FIELD
 import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.Nullability
 import com.squareup.javapoet.TypeVariableName
 
 internal class KspTypeParameterElement(
@@ -43,7 +45,16 @@
     }
 
     override val bounds: List<XType> by lazy {
-        declaration.bounds.map { env.wrap(it, it.resolve()) }.toList().ifEmpty {
+        declaration.bounds.map {
+            val type = it.resolve().let {
+                if (it.nullability == Nullability.PLATFORM) {
+                    it.withNullability(XNullability.NULLABLE)
+                } else {
+                    it
+                }
+            }
+            env.wrap(it, type)
+        }.toList().ifEmpty {
             listOf(env.requireType(Any::class).makeNullable())
         }
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index da629f7..46e753d 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.kruth.assertThat
 import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.compat.XConverters.toKS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asJClassName
@@ -1284,7 +1285,10 @@
             val kClassKTypeName = kotlin.reflect.KClass::class.asKClassName().parameterizedBy(STAR)
             fun checkSingleValue(annotationValue: XAnnotationValue, expectedValue: String) {
                 // TODO(bcorso): Consider making the value types match in this case.
-                if (!invocation.isKsp || (sourceKind == SourceKind.JAVA && !isPreCompiled)) {
+                if (!invocation.isKsp ||
+                        (invocation.processingEnv.toKS().kspVersion < KotlinVersion(2, 0) &&
+                        sourceKind == SourceKind.JAVA &&
+                        !isPreCompiled)) {
                     assertThat(annotationValue.valueType.asTypeName().java)
                         .isEqualTo(classJTypeName)
                 } else {
@@ -1299,7 +1303,10 @@
 
             fun checkListValues(annotationValue: XAnnotationValue, vararg expectedValues: String) {
                 // TODO(bcorso): Consider making the value types match in this case.
-                if (!invocation.isKsp || (sourceKind == SourceKind.JAVA && !isPreCompiled)) {
+                if (!invocation.isKsp ||
+                        (invocation.processingEnv.toKS().kspVersion < KotlinVersion(2, 0) &&
+                        sourceKind == SourceKind.JAVA &&
+                        !isPreCompiled)) {
                     assertThat(annotationValue.valueType.asTypeName().java)
                         .isEqualTo(JArrayTypeName.of(classJTypeName))
                 } else {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
index e83bbbe..9a83501 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
@@ -244,24 +244,31 @@
 
             validateMethodElement(
                 element = it.processingEnv.requireTypeElement("foo.bar.Base"),
-                tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
-                rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
+                tTypeName = XTypeName.getTypeVariableName("T", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true))),
+                rTypeName = XTypeName.getTypeVariableName("R", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true)))
             )
             validateMethodElement(
                 element = it.processingEnv.requireTypeElement("foo.bar.Child"),
-                tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
-                rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
+                tTypeName = XTypeName.getTypeVariableName("T", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true))),
+                rTypeName = XTypeName.getTypeVariableName("R", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true)))
             )
 
             validateMethodTypeAsMemberOf(
                 element = it.processingEnv.requireTypeElement("foo.bar.Base"),
-                tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
-                rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
+                tTypeName = XTypeName.getTypeVariableName("T", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true))),
+                rTypeName = XTypeName.getTypeVariableName("R", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true)))
             )
             validateMethodTypeAsMemberOf(
                 element = it.processingEnv.requireTypeElement("foo.bar.Child"),
                 tTypeName = String::class.asClassName(),
-                rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
+                rTypeName = XTypeName.getTypeVariableName("R", listOf(
+                    XTypeName.ANY_OBJECT.copy(nullable = true)))
             )
         }
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
index 4d9dcea..8320561 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -22,8 +22,9 @@
 import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getMethodByJvmName
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth
@@ -581,7 +582,8 @@
             """
             class KotlinSubject {
               fun <T> oneTypeVar(): Unit = TODO()
-              fun <T : MutableList<*>> oneBoundedTypeVar(): Unit = TODO()
+              fun <T : MutableList<*>?> oneBoundedTypeVar(): Unit = TODO()
+              fun <T : MutableList<*>> oneBoundedTypeVarNotNull(): Unit = TODO()
               fun <A, B> twoTypeVar(param: B): A = TODO()
             }
             """.trimIndent()
@@ -590,15 +592,18 @@
             "JavaSubject",
             """
             import java.util.List;
+            import org.jetbrains.annotations.NotNull;
             class JavaSubject {
               <T> void oneTypeVar() {}
               <T extends List<?>> void oneBoundedTypeVar() { }
+              <T extends @NotNull List<?>> void oneBoundedTypeVarNotNull() { }
               <A, B> A twoTypeVar(B param) { return null; }
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
-            listOf("KotlinSubject", "JavaSubject",).forEach { subjectFqn ->
+
+        fun handler(invocation: XTestInvocation) {
+            listOf("KotlinSubject", "JavaSubject").forEach { subjectFqn ->
                 val subject = invocation.processingEnv.requireTypeElement(subjectFqn)
                 subject.getMethodByJvmName("oneTypeVar").let {
                     val typeVar = it.executableType.typeVariables.single()
@@ -618,6 +623,29 @@
                                 bounds = listOf(
                                     List::class.asMutableClassName()
                                         .parametrizedBy(XTypeName.ANY_WILDCARD)
+                                        .copy(nullable = true)
+                                )
+                            )
+                        )
+                    assertThat(typeVar.superTypes.map { it.asTypeName() })
+                        .containsExactly(
+                            XTypeName.ANY_OBJECT.copy(nullable = true),
+                            List::class.asMutableClassName()
+                                .parametrizedBy(XTypeName.ANY_WILDCARD)
+                                .copy(nullable = true)
+                        )
+                    assertThat(typeVar.typeArguments).isEmpty()
+                    assertThat(typeVar.typeElement).isNull()
+                }
+                subject.getMethodByJvmName("oneBoundedTypeVarNotNull").let {
+                    val typeVar = it.executableType.typeVariables.single()
+                    assertThat(typeVar.asTypeName())
+                        .isEqualTo(
+                            XTypeName.getTypeVariableName(
+                                name = "T",
+                                bounds = listOf(
+                                    List::class.asMutableClassName()
+                                        .parametrizedBy(XTypeName.ANY_WILDCARD)
                                 )
                             )
                         )
@@ -646,5 +674,7 @@
                 }
             }
         }
+        runProcessorTest(sources = listOf(kotlinSrc, javaSrc), handler = ::handler)
+        runProcessorTest(classpath = compileFiles(listOf(kotlinSrc, javaSrc)), handler = ::handler)
     }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 4b190b5..f1dfb5f 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.processing.compat.XConverters.toKS
 import androidx.room.compiler.processing.javac.JavacType
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
@@ -1888,7 +1889,13 @@
                 // TODO(kuanyingchou): https://github.com/google/ksp/issues/1761
                 val parent = typeElement.superClass!!.typeElement!!
                 if (qName == "test.KotlinEnum" && !isPreCompiled && invocation.isKsp) {
-                    assertThat(parent.asClassName()).isEqualTo(Any::class.asClassName())
+                    if (invocation.isKsp &&
+                            invocation.processingEnv.toKS().kspVersion >=
+                            KotlinVersion(2, 0)) {
+                        assertThat(parent.asClassName()).isEqualTo(XTypeName.ENUM)
+                    } else {
+                        assertThat(parent.asClassName()).isEqualTo(Any::class.asClassName())
+                    }
                 } else {
                     assertThat(parent.asClassName()).isEqualTo(Enum::class.asClassName())
                 }
@@ -1896,17 +1903,17 @@
                 val methodNames = typeElement.getDeclaredMethods().map { it.jvmName }
                 if (qName == "test.KotlinEnum") {
                     if (invocation.isKsp) {
-                        if (isPreCompiled) {
+                        if (!isPreCompiled && invocation.processingEnv.toKS().kspVersion <
+                                KotlinVersion(2, 0)) {
+                            assertThat(methodNames).containsExactly(
+                                "enumMethod",
+                            )
+                        } else {
                             assertThat(methodNames).containsExactly(
                                 "enumMethod",
                                 "values",
                                 "valueOf",
                             )
-                        } else {
-                            // `values` and `valueOf` will be added in KSP2.
-                            assertThat(methodNames).containsExactly(
-                                "enumMethod",
-                            )
                         }
                     } else {
                         assertThat(methodNames).containsExactly(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeParameterElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeParameterElementTest.kt
index d14fea3..f3e289d 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeParameterElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeParameterElementTest.kt
@@ -182,20 +182,28 @@
             val bound0 = t.bounds[0]
             assertThat(bound0.asTypeName().java.toString()).isEqualTo("Bar")
             if (invocation.isKsp) {
-                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar")
+                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar?")
             }
-            val bar = invocation.processingEnv.requireType("Bar")
+            val bar = invocation.processingEnv.requireType("Bar").makeNullable()
             assertThat(bound0.isSameType(bar)).isTrue()
-            assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound0.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
 
             val bound1 = t.bounds[1]
             assertThat(bound1.asTypeName().java.toString()).isEqualTo("Baz")
             if (invocation.isKsp) {
-                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz")
+                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz?")
             }
-            val baz = invocation.processingEnv.requireType("Baz")
+            val baz = invocation.processingEnv.requireType("Baz").makeNullable()
             assertThat(bound1.isSameType(baz)).isTrue()
-            assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound1.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
         }
     }
 
@@ -333,20 +341,28 @@
             val bound0 = t.bounds[0]
             assertThat(bound0.asTypeName().java.toString()).isEqualTo("Bar")
             if (invocation.isKsp) {
-                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar")
+                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar?")
             }
-            val bar = invocation.processingEnv.requireType("Bar")
+            val bar = invocation.processingEnv.requireType("Bar").makeNullable()
             assertThat(bound0.isSameType(bar)).isTrue()
-            assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound0.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
 
             val bound1 = t.bounds[1]
             assertThat(bound1.asTypeName().java.toString()).isEqualTo("Baz")
             if (invocation.isKsp) {
-                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz")
+                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz?")
             }
-            val baz = invocation.processingEnv.requireType("Baz")
+            val baz = invocation.processingEnv.requireType("Baz").makeNullable()
             assertThat(bound1.isSameType(baz)).isTrue()
-            assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound1.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
         }
     }
 
@@ -381,20 +397,28 @@
             val bound0 = t.bounds[0]
             assertThat(bound0.asTypeName().java.toString()).isEqualTo("Bar")
             if (invocation.isKsp) {
-                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar")
+                assertThat(bound0.asTypeName().kotlin.toString()).isEqualTo("Bar?")
             }
-            val bar = invocation.processingEnv.requireType("Bar")
+            val bar = invocation.processingEnv.requireType("Bar").makeNullable()
             assertThat(bound0.isSameType(bar)).isTrue()
-            assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound0.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound0.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
 
             val bound1 = t.bounds[1]
             assertThat(bound1.asTypeName().java.toString()).isEqualTo("Baz")
             if (invocation.isKsp) {
-                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz")
+                assertThat(bound1.asTypeName().kotlin.toString()).isEqualTo("Baz?")
             }
-            val baz = invocation.processingEnv.requireType("Baz")
+            val baz = invocation.processingEnv.requireType("Baz").makeNullable()
             assertThat(bound1.isSameType(baz)).isTrue()
-            assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            if (invocation.isKsp) {
+                assertThat(bound1.nullability).isEqualTo(XNullability.NULLABLE)
+            } else {
+                assertThat(bound1.nullability).isEqualTo(XNullability.UNKNOWN)
+            }
 
             assertThat(constructor.parameters).hasSize(1)
             val parameter = constructor.parameters[0]
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 7b32220..30422ec 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
@@ -94,6 +94,7 @@
                             KTypeVariableName(
                                 "InputStreamType",
                                 KClassName("java.io", "InputStream")
+                                    .copy(nullable = true)
                             )
                         )
                 )
@@ -120,12 +121,14 @@
                     val expected = KTypeVariableName(
                         "InputStreamType",
                         KClassName("java.io", "InputStream")
+                            .copy(nullable = true)
                     )
                     assertThat(firstType.asTypeName().kotlin).isEqualTo(expected)
                     assertThat(
                         (firstType.asTypeName().kotlin as KTypeVariableName).bounds
                     ).containsExactly(
                         KClassName("java.io", "InputStream")
+                            .copy(nullable = true)
                     )
                 }
             }
@@ -622,8 +625,16 @@
             assertThat(typeElement.type.asTypeName().java.dumpToString(5))
                 .isEqualTo(expectedTypeStringDump)
             if (invocation.isKsp) {
+                val expectedTypeStringDumpKotlin = """
+                SelfReferencing<T>
+                | T
+                | > SelfReferencing<T>?
+                | > | T
+                | > | > SelfReferencing<T>?
+                | > | > | T
+                """.trimIndent()
                 assertThat(typeElement.type.asTypeName().kotlin.dumpToString(5))
-                    .isEqualTo(expectedTypeStringDump)
+                    .isEqualTo(expectedTypeStringDumpKotlin)
             }
             val expectedParamStringDump = """
                 SelfReferencing
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
index 2e69a12..5a051fd 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
@@ -94,15 +94,20 @@
             prepareDaosForWriting(databases, it.keys.toList())
             it.forEach { (daoMethod, db) ->
                 DaoWriter(
-                    daoMethod.dao,
-                    db.element,
-                    context.codeLanguage
+                    dao = daoMethod.dao,
+                    dbElement = db.element,
+                    codeLanguage = context.codeLanguage,
+                    javaLambdaSyntaxAvailable = context.javaLambdaSyntaxAvailable
                 ).write(context.processingEnv)
             }
         }
 
         databases?.forEach { db ->
-            DatabaseWriter(db, context.codeLanguage).write(context.processingEnv)
+            DatabaseWriter(
+                database = db,
+                codeLanguage = context.codeLanguage,
+                javaLambdaSyntaxAvailable = context.javaLambdaSyntaxAvailable
+            ).write(context.processingEnv)
             if (db.exportSchema) {
                 val qName = db.element.qualifiedName
                 val filename = "${db.version}.json"
@@ -131,8 +136,12 @@
                 }
             }
             db.autoMigrations.forEach { autoMigration ->
-                AutoMigrationWriter(db.element, autoMigration, context.codeLanguage)
-                    .write(context.processingEnv)
+                AutoMigrationWriter(
+                    autoMigration = autoMigration,
+                    dbElement = db.element,
+                    codeLanguage = context.codeLanguage,
+                    javaLambdaSyntaxAvailable = context.javaLambdaSyntaxAvailable
+                ).write(context.processingEnv)
             }
 
             if (context.codeLanguage == CodeLanguage.KOTLIN) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index 7b40165..f14ebe9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -22,12 +22,14 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.apply
+import androidx.room.compiler.codegen.XMemberName
 import androidx.room.compiler.codegen.XMemberName.Companion.companionMember
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.codegen.asMutableClassName
+import androidx.room.solver.CodeGenScope
 import com.squareup.kotlinpoet.javapoet.JTypeName
 import java.util.concurrent.Callable
 
@@ -405,6 +407,126 @@
 }.build()
 
 /**
+ * Generates a code block that invokes a function with a functional type as last parameter.
+ *
+ * For Java (jvmTarget >= 8) it will generate:
+ * ```
+ * <functionName>(<args>, (<lambdaSpec.paramName>) -> <lambdaSpec.body>);
+ * ```
+ * For Java (jvmTarget < 8) it will generate:
+ * ```
+ * <functionName>(<args>, new Function1<>() { <lambdaSpec.body> });
+ * ```
+ * For Kotlin it will generate:
+ * ```
+ * <functionName>(<args>) { <lambdaSpec.body> }
+ * ```
+ *
+ * The ideal usage of this utility function is to generate code that invokes the various
+ * `DBUtil.perform*()` APIs for interacting with the database connection in DAOs.
+ */
+fun InvokeWithLambdaParameter(
+    scope: CodeGenScope,
+    functionName: XMemberName,
+    argFormat: List<String>,
+    args: List<Any>,
+    continuationParamName: String? = null,
+    lambdaSpec: LambdaSpec
+): XCodeBlock = XCodeBlock.builder(scope.language).apply {
+    check(argFormat.size == args.size)
+    when (language) {
+        CodeLanguage.JAVA -> {
+            if (lambdaSpec.javaLambdaSyntaxAvailable) {
+                val argsFormatString = argFormat.joinToString(separator = ", ")
+                add(
+                    "%M($argsFormatString, (%L) -> {\n",
+                    functionName,
+                    *args.toTypedArray(),
+                    lambdaSpec.parameterName
+                )
+                indent()
+                val bodyScope = scope.fork()
+                with(lambdaSpec) { bodyScope.builder.body(bodyScope) }
+                add(bodyScope.generate())
+                unindent()
+                add("}")
+                if (continuationParamName != null) {
+                    add(", %L", continuationParamName)
+                }
+                add(");\n")
+            } else {
+                val adjustedArgsFormatString = buildList {
+                    addAll(argFormat)
+                    add("%L") // the anonymous function
+                    if (continuationParamName != null) {
+                        add("%L")
+                    }
+                }.joinToString(separator = ", ")
+                val adjustedArgs = buildList {
+                    addAll(args)
+                    add(
+                        Function1TypeSpec(
+                            language = language,
+                            parameterTypeName = lambdaSpec.parameterTypeName,
+                            parameterName = lambdaSpec.parameterName,
+                            returnTypeName = lambdaSpec.returnTypeName,
+                            callBody = {
+                                val bodyScope = scope.fork()
+                                with(lambdaSpec) { bodyScope.builder.body(bodyScope) }
+                                addCode(bodyScope.generate())
+                            }
+                        )
+                    )
+                    if (continuationParamName != null) {
+                        add(continuationParamName)
+                    }
+                }
+                add(
+                    "%M($adjustedArgsFormatString);\n",
+                    functionName,
+                    *adjustedArgs.toTypedArray(),
+                )
+            }
+        }
+        CodeLanguage.KOTLIN -> {
+            val argsFormatString = argFormat.joinToString(separator = ", ")
+            if (lambdaSpec.parameterTypeName.rawTypeName != KotlinTypeNames.CONTINUATION) {
+                add(
+                    "%M($argsFormatString) { %L ->\n",
+                    functionName,
+                    *args.toTypedArray(),
+                    lambdaSpec.parameterName
+                )
+            } else {
+                add(
+                    "%M($argsFormatString) {\n",
+                    functionName,
+                    *args.toTypedArray(),
+                )
+            }
+            indent()
+            val bodyScope = scope.fork()
+            with(lambdaSpec) { bodyScope.builder.body(bodyScope) }
+            add(bodyScope.generate())
+            unindent()
+            add("}\n")
+        }
+    }
+}.build()
+
+/**
+ * Describes the lambda to be generated with [InvokeWithLambdaParameter].
+ */
+abstract class LambdaSpec(
+    val parameterTypeName: XTypeName,
+    val parameterName: String,
+    val returnTypeName: XTypeName,
+    val javaLambdaSyntaxAvailable: Boolean
+) {
+    abstract fun XCodeBlock.Builder.body(scope: CodeGenScope)
+}
+
+/**
  * Generates an array literal with the given [values]
  *
  * Example: `ArrayLiteral(XTypeName.PRIMITIVE_INT, 1, 2, 3)`
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index 1e6377a..add440f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -99,6 +99,11 @@
         }
     }
 
+    // Whether Java 8's lambda syntax is available to be emitted or not.
+    val javaLambdaSyntaxAvailable by lazy {
+        processingEnv.jvmVersion >= 8
+    }
+
     companion object {
         val ARG_OPTIONS by lazy {
             ProcessorOptions.values().map { it.argName } +
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index da3bba1..31ea22f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -187,7 +187,6 @@
                 jvmMethodName = executableElement.jvmName,
                 callType = callType
             ),
-            javaLambdaSyntaxAvailable = context.processingEnv.jvmVersion >= 8
         )
 }
 
@@ -270,8 +269,7 @@
                 jvmMethodName = executableElement.jvmName,
                 callType = callType
             ),
-            continuationParamName = continuationParam.name,
-            javaLambdaSyntaxAvailable = context.processingEnv.jvmVersion >= 8
+            continuationParamName = continuationParam.name
         )
 
     private fun XCodeBlock.Builder.addCoroutineExecuteStatement(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 99041b3..5c711a60 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -89,7 +89,7 @@
     }
 
     fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "$columnName referenced in the primary key does not exists in the Entity." +
+        return "$columnName referenced in the primary key does not exist in the Entity." +
             " Available column names:${allColumns.joinToString(", ")}"
     }
 
@@ -389,7 +389,7 @@
     val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
 
     fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "$columnName referenced in the index does not exists in the Entity." +
+        return "$columnName referenced in the index does not exist in the Entity." +
             " Available column names:${allColumns.joinToString(", ")}"
     }
 
@@ -554,7 +554,7 @@
     val FOREIGN_KEY_CANNOT_FIND_PARENT = "Cannot find parent entity class."
 
     fun foreignKeyChildColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "($columnName) referenced in the foreign key does not exists in the Entity." +
+        return "($columnName) referenced in the foreign key does not exist in the Entity." +
             " Available column names:${allColumns.joinToString(", ")}"
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt
index e552cd7..a11d2dc 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.solver
 
-import androidx.annotation.VisibleForTesting
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.writer.TypeWriter
 
@@ -29,6 +28,7 @@
     val useDriverApi: Boolean = false
 ) {
     val language = writer.codeLanguage
+    val javaLambdaSyntaxAvailable = writer.javaLambdaSyntaxAvailable
     val builder by lazy { XCodeBlock.builder(language) }
     private val tmpVarIndices = mutableMapOf<String, Int>()
 
@@ -36,8 +36,7 @@
         const val TMP_VAR_DEFAULT_PREFIX = "_tmp"
         const val CLASS_PROPERTY_PREFIX = "__"
 
-        @VisibleForTesting
-        fun getTmpVarString(index: Int) =
+        internal fun getTmpVarString(index: Int) =
             getTmpVarString(TMP_VAR_DEFAULT_PREFIX, index)
 
         private fun getTmpVarString(prefix: String, index: Int) =
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt
index 5172cacb..71d890b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt
@@ -16,13 +16,14 @@
 
 package androidx.room.solver.prepared.binder
 
-import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.box
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -54,40 +55,21 @@
         returnTypeName: XTypeName,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> executeAndReturnJava(
-                sqlQueryVar, dbProperty, bindStatement, returnTypeName, scope
-            )
-            CodeLanguage.KOTLIN -> executeAndReturnKotlin(
-                sqlQueryVar, dbProperty, bindStatement, scope
-            )
-        }
-    }
-
-    private fun executeAndReturnJava(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        returnTypeName: XTypeName,
-        scope: CodeGenScope
-    ) {
         val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            // TODO(b/322387497): Generate lambda syntax if possible
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = returnTypeName.box()
+                returnTypeName = returnTypeName.box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val statementVar = scope.getTmpVar("_stmt")
                     addLocalVal(
                         statementVar,
                         SQLiteDriverTypeNames.STATEMENT,
@@ -96,49 +78,14 @@
                         sqlQueryVar
                     )
                     beginControlFlow("try")
-                    bindStatement(functionScope, statementVar)
-                    adapter?.executeAndReturn(connectionVar, statementVar, functionScope)
+                    bindStatement(scope, statementVar)
+                    adapter?.executeAndReturn(connectionVar, statementVar, scope)
                     nextControlFlow("finally")
                     addStatement("%L.close()", statementVar)
                     endControlFlow()
-                }.build()
-                this.addCode(functionCode)
-            },
-            continuationParamName
+                }
+            }
         )
-    }
-
-    private fun executeAndReturnKotlin(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        scope: CodeGenScope
-    ) {
-        val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            )
-            addLocalVal(
-                statementVar,
-                SQLiteDriverTypeNames.STATEMENT,
-                "%L.prepare(%L)",
-                connectionVar,
-                sqlQueryVar
-            )
-            beginControlFlow("try")
-            bindStatement(scope, statementVar)
-            adapter?.executeAndReturn(connectionVar, statementVar, scope)
-            nextControlFlow("finally")
-            addStatement("%L.close()", statementVar)
-            endControlFlow()
-            endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
index a7a3591..2b40e63 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
@@ -17,12 +17,14 @@
 package androidx.room.solver.prepared.binder
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.box
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -61,41 +63,20 @@
         returnTypeName: XTypeName,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> executeAndReturnJava(
-                sqlQueryVar, dbProperty, bindStatement, returnTypeName, scope
-            )
-            CodeLanguage.KOTLIN -> executeAndReturnKotlin(
-                sqlQueryVar, dbProperty, bindStatement, scope
-            )
-        }
-    }
-
-    private fun executeAndReturnJava(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        returnTypeName: XTypeName,
-        scope: CodeGenScope
-    ) {
         val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        val returnPrefix = if (returnTypeName == XTypeName.UNIT_VOID) "" else "return "
-        scope.builder.addStatement(
-            "$returnPrefix%M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            // TODO(b/322387497): Generate lambda syntax if possible
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = returnTypeName.box()
+                returnTypeName = returnTypeName.box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val statementVar = scope.getTmpVar("_stmt")
                     addLocalVal(
                         statementVar,
                         SQLiteDriverTypeNames.STATEMENT,
@@ -104,48 +85,18 @@
                         sqlQueryVar
                     )
                     beginControlFlow("try")
-                    bindStatement(functionScope, statementVar)
-                    adapter?.executeAndReturn(connectionVar, statementVar, functionScope)
+                    bindStatement(scope, statementVar)
+                    adapter?.executeAndReturn(connectionVar, statementVar, scope)
                     nextControlFlow("finally")
                     addStatement("%L.close()", statementVar)
                     endControlFlow()
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun executeAndReturnKotlin(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        scope: CodeGenScope
-    ) {
-        val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            )
-            addLocalVal(
-                statementVar,
-                SQLiteDriverTypeNames.STATEMENT,
-                "%L.prepare(%L)",
-                connectionVar,
-                sqlQueryVar
-            )
-            beginControlFlow("try")
-            bindStatement(scope, statementVar)
-            adapter?.executeAndReturn(connectionVar, statementVar, scope)
-            nextControlFlow("finally")
-            addStatement("%L.close()", statementVar)
-            endControlFlow()
-            endControlFlow()
+        val returnPrefix = when (scope.language) {
+            CodeLanguage.JAVA -> if (returnTypeName == XTypeName.UNIT_VOID) "" else "return "
+            CodeLanguage.KOTLIN -> "return "
         }
+        scope.builder.add("$returnPrefix%L", performBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
index 2a7ff10..89f6080 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
@@ -22,11 +22,13 @@
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.box
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.ArrayLiteral
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.ext.CommonTypeNames
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomCoroutinesTypeNames.COROUTINES_ROOM
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
@@ -99,53 +101,24 @@
             CommonTypeNames.STRING,
             *tableNames.toTypedArray()
         )
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                sqlQueryVar,
-                dbProperty,
-                bindStatement,
-                inTransaction,
-                arrayOfTableNamesLiteral,
-                scope
-            )
-
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                sqlQueryVar,
-                dbProperty,
-                bindStatement,
-                inTransaction,
-                arrayOfTableNamesLiteral,
-                scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        inTransaction: Boolean,
-        arrayOfTableNamesLiteral: XCodeBlock,
-        scope: CodeGenScope
-    ) {
         val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L)",
-            RoomTypeNames.FLOW_UTIL.packageMember("createFlow"),
-            dbProperty,
-            inTransaction,
-            arrayOfTableNamesLiteral,
-            // TODO(b/322387497): Generate lambda syntax if possible
-            Function1TypeSpec(
-                language = scope.language,
+        val createBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.FLOW_UTIL.packageMember("createFlow"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, inTransaction, arrayOfTableNamesLiteral),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = typeArg.asTypeName()
+                returnTypeName = returnTypeName.box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val outVar = functionScope.getTmpVar("_result")
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val returnPrefix = when (language) {
+                        CodeLanguage.JAVA -> "return "
+                        CodeLanguage.KOTLIN -> ""
+                    }
+                    val statementVar = scope.getTmpVar("_stmt")
                     addLocalVal(
                         statementVar,
                         SQLiteDriverTypeNames.STATEMENT,
@@ -154,53 +127,16 @@
                         sqlQueryVar
                     )
                     beginControlFlow("try")
-                    bindStatement(functionScope, statementVar)
-                    adapter?.convert(outVar, statementVar, functionScope)
-                    addStatement("return %L", outVar)
+                    bindStatement(scope, statementVar)
+                    val outVar = scope.getTmpVar("_result")
+                    adapter?.convert(outVar, statementVar, scope)
+                    addStatement("$returnPrefix%L", outVar)
                     nextControlFlow("finally")
                     addStatement("%L.close()", statementVar)
                     endControlFlow()
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        inTransaction: Boolean,
-        arrayOfTableNamesLiteral: XCodeBlock,
-        scope: CodeGenScope
-    ) {
-        val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.FLOW_UTIL.packageMember("createFlow"),
-                dbProperty,
-                inTransaction,
-                arrayOfTableNamesLiteral,
-                connectionVar
-            )
-            addLocalVal(
-                statementVar,
-                SQLiteDriverTypeNames.STATEMENT,
-                "%L.prepare(%L)",
-                connectionVar,
-                sqlQueryVar
-            )
-            beginControlFlow("try")
-            bindStatement(scope, statementVar)
-            val outVar = scope.getTmpVar("_result")
-            adapter?.convert(outVar, statementVar, scope)
-            addStatement("%L", outVar)
-            nextControlFlow("finally")
-            addStatement("%L.close()", statementVar)
-            endControlFlow()
-            endControlFlow()
-        }
+        scope.builder.add("return %L", createBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index e376b18..8d955de 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -27,7 +27,8 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.AndroidTypeNames
 import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomCoroutinesTypeNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
@@ -152,42 +153,25 @@
         inTransaction: Boolean,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                sqlQueryVar, dbProperty, bindStatement, returnTypeName, inTransaction, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                sqlQueryVar, dbProperty, bindStatement, inTransaction, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        returnTypeName: XTypeName,
-        inTransaction: Boolean,
-        scope: CodeGenScope
-    ) {
         val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-            dbProperty,
-            true, // isReadOnly
-            inTransaction,
-            // TODO(b/322387497): Generate lambda syntax if possible
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ true, inTransaction),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = returnTypeName.box()
+                returnTypeName = returnTypeName.box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val outVar = functionScope.getTmpVar("_result")
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val returnPrefix = when (language) {
+                        CodeLanguage.JAVA -> "return "
+                        CodeLanguage.KOTLIN -> ""
+                    }
+                    val statementVar = scope.getTmpVar("_stmt")
                     addLocalVal(
                         statementVar,
                         SQLiteDriverTypeNames.STATEMENT,
@@ -196,53 +180,16 @@
                         sqlQueryVar
                     )
                     beginControlFlow("try")
-                    bindStatement(functionScope, statementVar)
-                    adapter?.convert(outVar, statementVar, functionScope)
-                    addStatement("return %L", outVar)
+                    bindStatement(scope, statementVar)
+                    val outVar = scope.getTmpVar("_result")
+                    adapter?.convert(outVar, statementVar, scope)
+                    addStatement("$returnPrefix%L", outVar)
                     nextControlFlow("finally")
                     addStatement("%L.close()", statementVar)
                     endControlFlow()
-                }.build()
-                this.addCode(functionCode)
-            },
-            continuationParamName
+                }
+            }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        inTransaction: Boolean,
-        scope: CodeGenScope
-    ) {
-        val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-                dbProperty,
-                true, // isReadOnly
-                inTransaction,
-                connectionVar
-            )
-            scope.builder.addLocalVal(
-                statementVar,
-                SQLiteDriverTypeNames.STATEMENT,
-                "%L.prepare(%L)",
-                connectionVar,
-                sqlQueryVar
-            )
-            beginControlFlow("try")
-            bindStatement(scope, statementVar)
-            val outVar = scope.getTmpVar("_result")
-            adapter?.convert(outVar, statementVar, scope)
-            addStatement("%L", outVar)
-            nextControlFlow("finally")
-            addStatement("%L.close()", statementVar)
-            endControlFlow()
-            endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
index d947e00..dc58000 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
@@ -23,7 +23,8 @@
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.box
 import androidx.room.ext.AndroidTypeNames
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -91,42 +92,24 @@
         inTransaction: Boolean,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                sqlQueryVar, dbProperty, bindStatement, returnTypeName, inTransaction, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                sqlQueryVar, dbProperty, bindStatement, inTransaction, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        returnTypeName: XTypeName,
-        inTransaction: Boolean,
-        scope: CodeGenScope
-    ) {
         val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            true, // isReadOnly
-            inTransaction,
-            // TODO(b/322387497): Generate lambda syntax if possible
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ true, inTransaction),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = returnTypeName.box()
+                returnTypeName = returnTypeName.box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val outVar = functionScope.getTmpVar("_result")
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val returnPrefix = when (language) {
+                        CodeLanguage.JAVA -> "return "
+                        CodeLanguage.KOTLIN -> ""
+                    }
+                    val statementVar = scope.getTmpVar("_stmt")
                     addLocalVal(
                         statementVar,
                         SQLiteDriverTypeNames.STATEMENT,
@@ -135,52 +118,16 @@
                         sqlQueryVar
                     )
                     beginControlFlow("try")
-                    bindStatement(functionScope, statementVar)
-                    adapter?.convert(outVar, statementVar, functionScope)
-                    addStatement("return %L", outVar)
+                    bindStatement(scope, statementVar)
+                    val outVar = scope.getTmpVar("_result")
+                    adapter?.convert(outVar, statementVar, scope)
+                    addStatement("$returnPrefix%L", outVar)
                     nextControlFlow("finally")
                     addStatement("%L.close()", statementVar)
                     endControlFlow()
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        sqlQueryVar: String,
-        dbProperty: XPropertySpec,
-        bindStatement: CodeGenScope.(String) -> Unit,
-        inTransaction: Boolean,
-        scope: CodeGenScope
-    ) {
-        val connectionVar = scope.getTmpVar("_connection")
-        val statementVar = scope.getTmpVar("_stmt")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                true, // isReadOnly
-                inTransaction,
-                connectionVar
-            )
-            addLocalVal(
-                statementVar,
-                SQLiteDriverTypeNames.STATEMENT,
-                "%L.prepare(%L)",
-                connectionVar,
-                sqlQueryVar
-            )
-            beginControlFlow("try")
-            bindStatement(scope, statementVar)
-            val outVar = scope.getTmpVar("_result")
-            adapter?.convert(outVar, statementVar, scope)
-            addStatement("%L", outVar)
-            nextControlFlow("finally")
-            addStatement("%L.close()", statementVar)
-            endControlFlow()
-            endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index e7ff05d..e245f35 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -151,9 +151,7 @@
 
     override fun getDefaultIndexAdapter() = indexAdapter
 
-    override fun isMigratedToDriver(): Boolean {
-        return relationCollectors.isEmpty()
-    }
+    override fun isMigratedToDriver(): Boolean = relationCollectors.all { it.isMigratedToDriver() }
 
     data class PojoMapping(
         val pojo: Pojo,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt
index 6fd03bf..57a3081 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt
@@ -16,13 +16,14 @@
 
 package androidx.room.solver.shortcut.binder
 
-import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.box
 import androidx.room.compiler.processing.XType
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -44,80 +45,33 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
         val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
+                        scope = scope,
                         parameters = parameters,
                         adapters = adapters,
                         connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
-            },
-            continuationParamName
+                }
+            }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
-        }
-        val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    parameters = parameters,
-                    adapters = adapters,
-                    connectionVar = connectionVar
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt
index f1c92ca..aa711af2 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt
@@ -16,12 +16,13 @@
 
 package androidx.room.solver.shortcut.binder
 
-import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.box
 import androidx.room.compiler.processing.XType
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -43,80 +44,33 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
         val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
-                        connectionVar = connectionVar,
+                        scope = scope,
                         parameters = parameters,
-                        adapters = adapters
+                        adapters = adapters,
+                        connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
-            },
-            continuationParamName
+                }
+            }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
-        }
-        val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    connectionVar = connectionVar,
-                    parameters = parameters,
-                    adapters = adapters
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt
index d820e4d..e572b89e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt
@@ -16,12 +16,13 @@
 
 package androidx.room.solver.shortcut.binder
 
-import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.box
 import androidx.room.compiler.processing.XType
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -43,81 +44,33 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
         val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
-                        connectionVar = connectionVar,
+                        scope = scope,
                         parameters = parameters,
-                        adapters = adapters
+                        adapters = adapters,
+                        connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
-            },
-            continuationParamName
+                }
+            }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
-        }
-        val connectionVar = scope.getTmpVar("_connection")
-
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    connectionVar = connectionVar,
-                    parameters = parameters,
-                    adapters = adapters
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
index 7ce2c3d..19b8ede 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
@@ -17,11 +17,13 @@
 package androidx.room.solver.shortcut.binder
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.box
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.isNotVoid
@@ -42,81 +44,36 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
-
-        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
         val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.addStatement(
-            "$returnPrefix%M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
+                        scope = scope,
                         parameters = parameters,
                         adapters = adapters,
                         connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
+        val returnPrefix = when (scope.language) {
+            CodeLanguage.JAVA -> if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+            CodeLanguage.KOTLIN -> "return "
         }
-        val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    parameters = parameters,
-                    adapters = adapters,
-                    connectionVar = connectionVar
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("$returnPrefix%L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
index ca27b5f..e1a4c9a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
@@ -17,10 +17,12 @@
 package androidx.room.solver.shortcut.binder
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.box
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.isNotVoid
@@ -40,81 +42,36 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
-        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
-
         val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.addStatement(
-            "$returnPrefix%M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
-                        connectionVar = connectionVar,
+                        scope = scope,
                         parameters = parameters,
-                        adapters = adapters
+                        adapters = adapters,
+                        connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
+        val returnPrefix = when (scope.language) {
+            CodeLanguage.JAVA -> if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+            CodeLanguage.KOTLIN -> "return "
         }
-        val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    connectionVar = connectionVar,
-                    parameters = parameters,
-                    adapters = adapters
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("$returnPrefix%L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
index 244e6c7..4d8be411 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
@@ -17,10 +17,12 @@
 package androidx.room.solver.shortcut.binder
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.box
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.isNotVoid
@@ -40,80 +42,36 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> convertAndReturnJava(
-                parameters, adapters, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
-                parameters, adapters, dbProperty, scope
-            )
-        }
-    }
-
-    private fun convertAndReturnJava(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         if (adapter == null) {
             return
         }
         val connectionVar = scope.getTmpVar("_connection")
-        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
-        scope.builder.addStatement(
-            "$returnPrefix%M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
                 parameterName = connectionVar,
-                returnTypeName = adapter.returnType.asTypeName().box()
+                returnTypeName = adapter.returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                val functionScope = scope.fork()
-                val functionCode = functionScope.builder.apply {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
                     adapter.generateMethodBody(
-                        scope = functionScope,
-                        connectionVar = connectionVar,
+                        scope = scope,
                         parameters = parameters,
-                        adapters = adapters
+                        adapters = adapters,
+                        connectionVar = connectionVar
                     )
-                }.build()
-                this.addCode(functionCode)
+                }
             }
         )
-    }
-
-    private fun convertAndReturnKotlin(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        if (adapter == null) {
-            return
+        val returnPrefix = when (scope.language) {
+            CodeLanguage.JAVA -> if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+            CodeLanguage.KOTLIN -> "return "
         }
-        val connectionVar = scope.getTmpVar("_connection")
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) { %L ->",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-                connectionVar
-            ).apply {
-                adapter.generateMethodBody(
-                    scope = scope,
-                    connectionVar = connectionVar,
-                    parameters = parameters,
-                    adapters = adapters
-                )
-            }.endControlFlow()
-        }
+        scope.builder.add("$returnPrefix%L", performBlock)
     }
 
     override fun convertAndReturnCompat(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
index bcaf3e3..62ce592 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
@@ -19,13 +19,13 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XType
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
 import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.transaction.result.TransactionMethodAdapter
@@ -36,8 +36,7 @@
 class CoroutineTransactionMethodBinder(
     private val returnType: XType,
     adapter: TransactionMethodAdapter,
-    private val continuationParamName: String,
-    private val javaLambdaSyntaxAvailable: Boolean
+    private val continuationParamName: String
 ) : TransactionMethodBinder(adapter) {
     override fun executeAndReturn(
         parameterNames: List<String>,
@@ -46,81 +45,40 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> executeAndReturnJava(
-                parameterNames, daoName, daoImplName, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> executeAndReturnKotlin(
-                parameterNames, daoName, daoImplName, dbProperty, scope
-            )
-        }
-    }
-
-    private fun executeAndReturnJava(
-        parameterNames: List<String>,
-        daoName: XClassName,
-        daoImplName: XClassName,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
         val innerContinuationParamName = scope.getTmpVar("_cont")
-        val adapterScope = scope.fork()
-        adapter.createDelegateToSuperCode(
-            parameterNames = parameterNames + innerContinuationParamName,
-            daoName = daoName,
-            daoImplName = daoImplName,
-            scope = adapterScope
-        )
-        val functionImpl: Any = if (javaLambdaSyntaxAvailable) {
-            XCodeBlock.of(
-                scope.language,
-                "(%L) -> %L",
-                innerContinuationParamName, adapterScope.generate()
-            )
-        } else {
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performInTransactionSuspending"),
+            argFormat = listOf("%N"),
+            args = listOf(dbProperty),
+            continuationParamName = continuationParamName,
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = KotlinTypeNames.CONTINUATION.parametrizedBy(
                     XTypeName.getConsumerSuperName(returnType.asTypeName())
                 ),
                 parameterName = innerContinuationParamName,
-                returnTypeName = KotlinTypeNames.ANY
+                returnTypeName = KotlinTypeNames.ANY,
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                addStatement("return %L", adapterScope.generate())
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val adapterScope = scope.fork()
+                    adapter.createDelegateToSuperCode(
+                        parameterNames = when (scope.language) {
+                            CodeLanguage.JAVA -> parameterNames + innerContinuationParamName
+                            CodeLanguage.KOTLIN -> parameterNames
+                        },
+                        daoName = daoName,
+                        daoImplName = daoImplName,
+                        scope = adapterScope
+                    )
+                    val returnPrefix = when (scope.language) {
+                        CodeLanguage.JAVA -> "return "
+                        CodeLanguage.KOTLIN -> ""
+                    }
+                    addStatement("$returnPrefix%L", adapterScope.generate())
+                }
             }
-        }
-
-        scope.builder.addStatement(
-            "return %M(%N, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performInTransactionSuspending"),
-            dbProperty,
-            functionImpl,
-            continuationParamName
         )
-    }
-
-    private fun executeAndReturnKotlin(
-        parameterNames: List<String>,
-        daoName: XClassName,
-        daoImplName: XClassName,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N) {",
-                RoomTypeNames.DB_UTIL.packageMember("performInTransactionSuspending"),
-                dbProperty,
-            )
-            val adapterScope = scope.fork()
-            adapter.createDelegateToSuperCode(
-                parameterNames = parameterNames,
-                daoName = daoName,
-                daoImplName = daoImplName,
-                scope = adapterScope
-            )
-            addStatement("%L", adapterScope.generate())
-            endControlFlow()
-        }
+        scope.builder.add("return %L", performBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
index ad1e9b3..c458972 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
@@ -19,15 +19,15 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.box
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isKotlinUnit
 import androidx.room.compiler.processing.isVoid
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
 import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
@@ -39,7 +39,6 @@
 class InstantTransactionMethodBinder(
     private val returnType: XType,
     adapter: TransactionMethodAdapter,
-    private val javaLambdaSyntaxAvailable: Boolean
 ) : TransactionMethodBinder(adapter) {
     override fun executeAndReturn(
         parameterNames: List<String>,
@@ -48,88 +47,47 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        when (scope.language) {
-            CodeLanguage.JAVA -> executeAndReturnJava(
-                parameterNames, daoName, daoImplName, dbProperty, scope
-            )
-            CodeLanguage.KOTLIN -> executeAndReturnKotlin(
-                parameterNames, daoName, daoImplName, dbProperty, scope
-            )
+        val returnPrefix = when (scope.language) {
+            CodeLanguage.JAVA ->
+                if (returnType.isVoid() || returnType.isKotlinUnit()) "" else "return "
+            CodeLanguage.KOTLIN -> "return "
         }
-    }
-
-    private fun executeAndReturnJava(
-        parameterNames: List<String>,
-        daoName: XClassName,
-        daoImplName: XClassName,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        val adapterScope = scope.fork()
-        val returnPrefix = if (returnType.isVoid() || returnType.isKotlinUnit()) "" else "return "
-        adapter.createDelegateToSuperCode(
-            parameterNames = parameterNames,
-            daoName = daoName,
-            daoImplName = daoImplName,
-            scope = adapterScope
-        )
-        val connectionVar = scope.getTmpVar("_connection")
-        val functionImpl: Any = if (javaLambdaSyntaxAvailable) {
-            XCodeBlock.builder(scope.language).apply {
-                add("(%L) -> {\n", connectionVar)
-                add("%>$returnPrefix%L;\n", adapterScope.generate())
-                if (returnPrefix.isEmpty()) {
-                    add("return %T.INSTANCE;\n", KotlinTypeNames.UNIT)
-                }
-                add("%<}")
-            }.build()
-        } else {
-            Function1TypeSpec(
-                language = scope.language,
+        val performBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            argFormat = listOf("%N", "%L", "%L"),
+            args = listOf(dbProperty, /* isReadOnly = */ false, /* inTransaction = */ true),
+            lambdaSpec = object : LambdaSpec(
                 parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
-                parameterName = connectionVar,
-                returnTypeName = returnType.asTypeName().box()
+                parameterName = when (scope.language) {
+                    CodeLanguage.JAVA -> scope.getTmpVar("_connection")
+                    CodeLanguage.KOTLIN -> "_"
+                },
+                returnTypeName = returnType.asTypeName().box(),
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
             ) {
-                this.addStatement("$returnPrefix%L", adapterScope.generate())
-                if (returnPrefix.isEmpty()) {
-                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val adapterScope = scope.fork()
+                    adapter.createDelegateToSuperCode(
+                        parameterNames = parameterNames,
+                        daoName = daoName,
+                        daoImplName = daoImplName,
+                        scope = adapterScope
+                    )
+                    when (scope.language) {
+                        CodeLanguage.JAVA -> {
+                            addStatement("$returnPrefix%L", adapterScope.generate())
+                            if (returnPrefix.isEmpty()) {
+                                addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+                            }
+                        }
+                        CodeLanguage.KOTLIN -> {
+                            addStatement("%L", adapterScope.generate())
+                        }
+                    }
                 }
             }
-        }
-        scope.builder.addStatement(
-            "$returnPrefix%M(%N, %L, %L, %L)",
-            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-            dbProperty,
-            false, // isReadOnly
-            true, // inTransaction
-            functionImpl
         )
-    }
-
-    private fun executeAndReturnKotlin(
-        parameterNames: List<String>,
-        daoName: XClassName,
-        daoImplName: XClassName,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        scope.builder.apply {
-            beginControlFlow(
-                "return %M(%N, %L, %L) {",
-                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
-                dbProperty,
-                false, // isReadOnly
-                true, // inTransaction
-            )
-            val adapterScope = scope.fork()
-            adapter.createDelegateToSuperCode(
-                parameterNames = parameterNames,
-                daoName = daoName,
-                daoImplName = daoImplName,
-                scope = adapterScope
-            )
-            addStatement("%L", adapterScope.generate())
-            endControlFlow()
-        }
+        scope.builder.add("$returnPrefix%L", performBlock)
     }
 }
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 eceddad..ba77eb5 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
@@ -22,8 +22,15 @@
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XNullability
-import androidx.room.ext.CollectionTypeNames
+import androidx.room.ext.CollectionTypeNames.ARRAY_MAP
+import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.CommonTypeNames.ARRAY_LIST
+import androidx.room.ext.CommonTypeNames.HASH_MAP
+import androidx.room.ext.CommonTypeNames.HASH_SET
+import androidx.room.ext.KotlinCollectionMemberNames
+import androidx.room.ext.KotlinCollectionMemberNames.MUTABLE_LIST_OF
+import androidx.room.ext.KotlinCollectionMemberNames.MUTABLE_SET_OF
 import androidx.room.ext.capitalize
 import androidx.room.ext.stripNonJava
 import androidx.room.parser.ParsedQuery
@@ -43,6 +50,7 @@
 import androidx.room.verifier.DatabaseVerificationErrors
 import androidx.room.writer.QueryWriter
 import androidx.room.writer.RelationCollectorFunctionWriter
+import androidx.room.writer.RelationCollectorFunctionWriter.Companion.PARAM_CONNECTION_VARIABLE
 import java.util.Locale
 
 /**
@@ -69,23 +77,37 @@
     // parsed relating entity query
     val loadAllQuery: ParsedQuery,
     // true if `relationTypeName` is a Collection, when it is `relationTypeName` is always non null.
-    val relationTypeIsCollection: Boolean,
-    val javaLambdaSyntaxAvailable: Boolean
+    val relationTypeIsCollection: Boolean
 ) {
+    // TODO(b/319660042): Remove once migration to driver API is done.
+    fun isMigratedToDriver(): Boolean = rowAdapter.isMigratedToDriver()
+
     // variable name of map containing keys to relation collections, set when writing the code
     // generator in writeInitCode
-    lateinit var varName: String
+    private lateinit var varName: String
 
     fun writeInitCode(scope: CodeGenScope) {
         varName = scope.getTmpVar(
             "_collection${relation.field.getPath().stripNonJava().capitalize(Locale.US)}"
         )
         scope.builder.apply {
-            addLocalVariable(
-                name = varName,
-                typeName = mapTypeName,
-                assignExpr = XCodeBlock.ofNewInstance(language, mapTypeName)
-            )
+            if (language == CodeLanguage.JAVA ||
+                mapTypeName.rawTypeName == ARRAY_MAP ||
+                mapTypeName.rawTypeName == LONG_SPARSE_ARRAY
+            ) {
+                addLocalVariable(
+                    name = varName,
+                    typeName = mapTypeName,
+                    assignExpr = XCodeBlock.ofNewInstance(language, mapTypeName)
+                )
+            } else {
+                addLocalVal(
+                    name = varName,
+                    typeName = mapTypeName,
+                    "%M()",
+                    KotlinCollectionMemberNames.MUTABLE_MAP_OF
+                )
+            }
         }
     }
 
@@ -106,9 +128,25 @@
                 // for relation collection put an empty collections in the map, otherwise put nulls
                 if (relationTypeIsCollection) {
                     beginControlFlow("if (!%L.containsKey(%L))", varName, tmpVar).apply {
+                        val newEmptyCollection = when (language) {
+                            CodeLanguage.JAVA ->
+                                XCodeBlock.ofNewInstance(language, relationTypeName)
+                            CodeLanguage.KOTLIN ->
+                                XCodeBlock.of(
+                                    language = language,
+                                    "%M()",
+                                    if (relationTypeName == CommonTypeNames.MUTABLE_SET) {
+                                        MUTABLE_SET_OF
+                                    } else {
+                                        MUTABLE_LIST_OF
+                                    }
+                                )
+                        }
                         addStatement(
                             "%L.put(%L, %L)",
-                            varName, tmpVar, XCodeBlock.ofNewInstance(language, relationTypeName)
+                            varName,
+                            tmpVar,
+                            newEmptyCollection
                         )
                     }
                     endControlFlow()
@@ -152,7 +190,7 @@
                         // values for all keys, so this is safe. Special case for LongSParseArray
                         // since it does not have a getValue() from Kotlin.
                         val usingLongSparseArray =
-                            mapTypeName.rawTypeName == CollectionTypeNames.LONG_SPARSE_ARRAY
+                            mapTypeName.rawTypeName == LONG_SPARSE_ARRAY
                         when (language) {
                             CodeLanguage.JAVA -> addStatement(
                                 "%L = %L.get(%L)",
@@ -187,9 +225,23 @@
                 },
                 >
                     if (relationTypeIsCollection) {
+                        val newEmptyCollection = when (language) {
+                            CodeLanguage.JAVA ->
+                                XCodeBlock.ofNewInstance(language, relationTypeName)
+                            CodeLanguage.KOTLIN ->
+                                XCodeBlock.of(
+                                    language = language,
+                                    "%M()",
+                                    if (relationTypeName == CommonTypeNames.MUTABLE_SET) {
+                                        MUTABLE_SET_OF
+                                    } else {
+                                        MUTABLE_LIST_OF
+                                    }
+                                )
+                        }
                         addStatement(
                             "%L = %L",
-                            tmpRelationVar, XCodeBlock.ofNewInstance(language, relationTypeName)
+                            tmpRelationVar, newEmptyCollection
                         )
                     } else {
                         addStatement("%L = null", tmpRelationVar)
@@ -203,8 +255,14 @@
     // called to write the invocation to the fetch relationship method
     fun writeFetchRelationCall(scope: CodeGenScope) {
         val method = scope.writer
-            .getOrCreateFunction(RelationCollectorFunctionWriter(this))
-        scope.builder.addStatement("%L(%L)", method.name, varName)
+            .getOrCreateFunction(RelationCollectorFunctionWriter(this, scope.useDriverApi))
+        scope.builder.apply {
+            if (scope.useDriverApi) {
+                addStatement("%L(%L, %L)", method.name, PARAM_CONNECTION_VARIABLE, varName)
+            } else {
+                addStatement("%L(%L)", method.name, varName)
+            }
+        }
     }
 
     // called to read key and call `onKeyReady` to write code once it is successfully read
@@ -339,10 +397,10 @@
                 val resultInfo = parsedQuery.resultInfo
 
                 val usingLongSparseArray =
-                    tmpMapTypeName.rawTypeName == CollectionTypeNames.LONG_SPARSE_ARRAY
+                    tmpMapTypeName.rawTypeName == LONG_SPARSE_ARRAY
                 val queryParam = if (usingLongSparseArray) {
                     val longSparseArrayElement = context.processingEnv
-                        .requireTypeElement(CollectionTypeNames.LONG_SPARSE_ARRAY.canonicalName)
+                        .requireTypeElement(LONG_SPARSE_ARRAY.canonicalName)
                     QueryParameter(
                         name = RelationCollectorFunctionWriter.PARAM_MAP_VARIABLE,
                         sqlName = RelationCollectorFunctionWriter.PARAM_MAP_VARIABLE,
@@ -433,8 +491,7 @@
                         entityKeyColumnReader = entityKeyColumnReader,
                         rowAdapter = rowAdapter,
                         loadAllQuery = parsedQuery,
-                        relationTypeIsCollection = isRelationCollection,
-                        javaLambdaSyntaxAvailable = context.processingEnv.jvmVersion >= 8
+                        relationTypeIsCollection = isRelationCollection
                     )
                 }
             }.filterNotNull()
@@ -505,13 +562,20 @@
             if (fieldType.typeArguments.isNotEmpty()) {
                 val rawType = fieldType.rawType
                 val paramTypeName =
-                    if (context.COMMON_TYPES.LIST.rawType.isAssignableFrom(rawType)) {
-                        CommonTypeNames.ARRAY_LIST.parametrizedBy(relation.pojoTypeName)
-                    } else if (context.COMMON_TYPES.SET.rawType.isAssignableFrom(rawType)) {
-                        CommonTypeNames.HASH_SET.parametrizedBy(relation.pojoTypeName)
+                    if (context.COMMON_TYPES.SET.rawType.isAssignableFrom(rawType)) {
+                        when (context.codeLanguage) {
+                            CodeLanguage.KOTLIN ->
+                                CommonTypeNames.MUTABLE_SET.parametrizedBy(relation.pojoTypeName)
+                            CodeLanguage.JAVA ->
+                                HASH_SET.parametrizedBy(relation.pojoTypeName)
+                        }
                     } else {
-                        // Default to ArrayList and see how things go...
-                        CommonTypeNames.ARRAY_LIST.parametrizedBy(relation.pojoTypeName)
+                        when (context.codeLanguage) {
+                            CodeLanguage.KOTLIN ->
+                                CommonTypeNames.MUTABLE_LIST.parametrizedBy(relation.pojoTypeName)
+                            CodeLanguage.JAVA ->
+                                ARRAY_LIST.parametrizedBy(relation.pojoTypeName)
+                        }
                     }
                 paramTypeName to true
             } else {
@@ -526,24 +590,30 @@
             keyTypeName: XTypeName,
             valueTypeName: XTypeName,
         ): XTypeName {
-            val canUseLongSparseArray = context.processingEnv
-                .findTypeElement(CollectionTypeNames.LONG_SPARSE_ARRAY.canonicalName) != null
-            val canUseArrayMap = context.processingEnv
-                .findTypeElement(CollectionTypeNames.ARRAY_MAP.canonicalName) != null
+            val canUseLongSparseArray =
+                context.processingEnv
+                    .findTypeElement(LONG_SPARSE_ARRAY.canonicalName) != null
+            val canUseArrayMap =
+                context.processingEnv
+                    .findTypeElement(ARRAY_MAP.canonicalName) != null
             return when {
                 canUseLongSparseArray && affinity == SQLTypeAffinity.INTEGER ->
-                    CollectionTypeNames.LONG_SPARSE_ARRAY.parametrizedBy(valueTypeName)
+                    LONG_SPARSE_ARRAY.parametrizedBy(valueTypeName)
                 canUseArrayMap ->
-                    CollectionTypeNames.ARRAY_MAP.parametrizedBy(keyTypeName, valueTypeName)
-                else ->
-                    CommonTypeNames.HASH_MAP.parametrizedBy(keyTypeName, valueTypeName)
+                    ARRAY_MAP.parametrizedBy(keyTypeName, valueTypeName)
+                else -> when (context.codeLanguage) {
+                    CodeLanguage.JAVA ->
+                        HASH_MAP.parametrizedBy(keyTypeName, valueTypeName)
+                    CodeLanguage.KOTLIN ->
+                        CommonTypeNames.MUTABLE_MAP.parametrizedBy(keyTypeName, valueTypeName)
+                }
             }
         }
 
         // Gets the type name of the relationship key.
         private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): XTypeName {
             val canUseLongSparseArray = context.processingEnv
-                .findTypeElement(CollectionTypeNames.LONG_SPARSE_ARRAY.canonicalName) != null
+                .findTypeElement(LONG_SPARSE_ARRAY.canonicalName) != null
             return when (affinity) {
                 SQLTypeAffinity.INTEGER ->
                     if (canUseLongSparseArray) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index ab93879..1ddb717 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -38,10 +38,11 @@
  * Writes the implementation of migrations that were annotated with @AutoMigration.
  */
 class AutoMigrationWriter(
+    private val autoMigration: AutoMigration,
     private val dbElement: XTypeElement,
-    val autoMigration: AutoMigration,
-    codeLanguage: CodeLanguage
-) : TypeWriter(codeLanguage) {
+    codeLanguage: CodeLanguage,
+    javaLambdaSyntaxAvailable: Boolean
+) : TypeWriter(codeLanguage, javaLambdaSyntaxAvailable) {
     private val addedColumns = autoMigration.schemaDiff.addedColumns
     private val addedTables = autoMigration.schemaDiff.addedTables
     private val renamedTables = autoMigration.schemaDiff.renamedTables
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 7a610ad..352ab40 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -65,8 +65,9 @@
 class DaoWriter(
     val dao: Dao,
     private val dbElement: XElement,
-    codeLanguage: CodeLanguage
-) : TypeWriter(codeLanguage) {
+    codeLanguage: CodeLanguage,
+    javaLambdaSyntaxAvailable: Boolean
+) : TypeWriter(codeLanguage, javaLambdaSyntaxAvailable) {
     private val declaredDao = dao.element.type
 
     // TODO nothing prevents this from conflicting, we should fix.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index 82bd6f6..44206f5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -44,8 +44,9 @@
  */
 class DatabaseWriter(
     val database: Database,
-    codeLanguage: CodeLanguage
-) : TypeWriter(codeLanguage) {
+    codeLanguage: CodeLanguage,
+    javaLambdaSyntaxAvailable: Boolean
+) : TypeWriter(codeLanguage, javaLambdaSyntaxAvailable) {
     override fun createTypeSpecBuilder(): XTypeSpec.Builder {
         return XTypeSpec.classBuilder(codeLanguage, database.implTypeName).apply {
             addOriginatingElement(database.element)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
index 1ecfd81..eb0d29c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
@@ -20,18 +20,20 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XFunSpec
-import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.ext.AndroidTypeNames
 import androidx.room.ext.CollectionTypeNames
 import androidx.room.ext.CollectionsSizeExprCode
 import androidx.room.ext.CommonTypeNames
-import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
 import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.MapKeySetExprCode
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.RoomTypeNames.RELATION_UTIL
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.stripNonJava
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.query.result.PojoRowAdapter
@@ -41,13 +43,20 @@
  * Writes the function that fetches the relations of a POJO and assigns them into the given map.
  */
 class RelationCollectorFunctionWriter(
-    private val collector: RelationCollector
+    private val collector: RelationCollector,
+    private val useDriverApi: Boolean
 ) : TypeWriter.SharedFunctionSpec(
-    "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
-        "As${collector.relation.pojoTypeName.toString(CodeLanguage.JAVA).stripNonJava()}"
+    baseName = if (useDriverApi) {
+        "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
+            "As${collector.relation.pojoTypeName.toString(CodeLanguage.JAVA).stripNonJava()}"
+    } else {
+        "fetchCompatRelationship${collector.relation.entity.tableName.stripNonJava()}" +
+            "As${collector.relation.pojoTypeName.toString(CodeLanguage.JAVA).stripNonJava()}"
+    },
 ) {
     companion object {
         const val PARAM_MAP_VARIABLE = "_map"
+        const val PARAM_CONNECTION_VARIABLE = "_connection"
         const val KEY_SET_VARIABLE = "__mapKeySet"
     }
 
@@ -63,11 +72,12 @@
             "-${relation.entity.typeName.toString(CodeLanguage.JAVA)}" +
             "-${relation.entityField.columnName}" +
             "-${relation.pojoTypeName}" +
-            "-${relation.createLoadAllSql()}"
+            "-${relation.createLoadAllSql()}" +
+            "-$useDriverApi"
     }
 
     override fun prepare(methodName: String, writer: TypeWriter, builder: XFunSpec.Builder) {
-        val scope = CodeGenScope(writer)
+        val scope = CodeGenScope(writer = writer, useDriverApi = useDriverApi)
         scope.builder.apply {
             // Check the input map key set for emptiness, returning early as no fetching is needed.
             addIsInputEmptyCheck()
@@ -75,25 +85,59 @@
             // Check if the input map key set exceeds MAX_BIND_PARAMETER_CNT, if so do a recursive
             // fetch.
             beginControlFlow(
-                "if (%L > %T.MAX_BIND_PARAMETER_CNT)",
+                "if (%L > %L)",
                 if (usingLongSparseArray) {
                     XCodeBlock.of(language, "%L.size()", PARAM_MAP_VARIABLE)
                 } else {
                     CollectionsSizeExprCode(language, PARAM_MAP_VARIABLE)
                 },
-                RoomTypeNames.ROOM_DB
+                if (useDriverApi) {
+                    "999"
+                } else {
+                    XCodeBlock.of(
+                        language,
+                        "%T.MAX_BIND_PARAMETER_CNT",
+                        RoomTypeNames.ROOM_DB
+                    )
+                }
             ).apply {
-                addRecursiveFetchCall(methodName)
+                addRecursiveFetchCall(scope, methodName)
                 addStatement("return")
             }.endControlFlow()
 
-            // Create SQL query, acquire statement and bind parameters.
-            val stmtVar = scope.getTmpVar("_stmt")
-            val sqlQueryVar = scope.getTmpVar("_sql")
-            collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
+            createStmtAndReturn(scope)
+        }
+        builder.apply {
+            if (useDriverApi) {
+                addParameter(SQLiteDriverTypeNames.CONNECTION, PARAM_CONNECTION_VARIABLE)
+            }
+            addParameter(collector.mapTypeName, PARAM_MAP_VARIABLE)
+            addCode(scope.generate())
+        }
+    }
 
+    private fun XCodeBlock.Builder.createStmtAndReturn(
+        scope: CodeGenScope
+    ) {
+        // Create SQL query, acquire statement and bind parameters.
+        val stmtVar = scope.getTmpVar("_stmt")
+        val cursorVar = "_cursor"
+        val sqlQueryVar = scope.getTmpVar("_sql")
+
+        if (useDriverApi) {
+            val connectionVar = scope.getTmpVar(PARAM_CONNECTION_VARIABLE)
+            val listSizeVars = collector.queryWriter.prepareQuery(sqlQueryVar, scope)
+            addLocalVal(
+                stmtVar,
+                SQLiteDriverTypeNames.STATEMENT,
+                "%L.prepare(%L)",
+                connectionVar,
+                sqlQueryVar
+            )
+            collector.queryWriter.bindArgs(stmtVar, listSizeVars, scope)
+        } else {
+            collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
             // Perform query and get a Cursor
-            val cursorVar = "_cursor"
             val shouldCopyCursor = collector.rowAdapter.let {
                 it is PojoRowAdapter && it.relationCollectors.isNotEmpty()
             }
@@ -110,86 +154,91 @@
                     "null"
                 )
             )
+        }
+        addRelationCollectorCode(scope, if (useDriverApi) stmtVar else cursorVar)
+    }
 
-            val relation = collector.relation
-            beginControlFlow("try").apply {
-                // Gets index of the column to be used as key
-                val itemKeyIndexVar = "_itemKeyIndex"
-                if (relation.junction != null) {
-                    // When using a junction table the relationship map is keyed on the parent
-                    // reference column of the junction table, the same column used in the WHERE IN
-                    // clause, this column is the rightmost column in the generated SELECT
-                    // clause.
-                    val junctionParentColumnIndex = relation.projection.size
-                    addStatement("// _junction.%L", relation.junction.parentField.columnName)
-                    addLocalVal(
-                        itemKeyIndexVar,
-                        XTypeName.PRIMITIVE_INT,
-                        "%L",
-                        junctionParentColumnIndex
-                    )
-                } else {
-                    addLocalVal(
-                        itemKeyIndexVar,
-                        XTypeName.PRIMITIVE_INT,
-                        "%M(%L, %S)",
-                        RoomMemberNames.CURSOR_UTIL_GET_COLUMN_INDEX,
-                        cursorVar,
-                        relation.entityField.columnName
-                    )
-                }
-                // Check if index of column is not -1, indicating the column for the key is not in
-                // the result, can happen if the user specified a bad projection in @Relation.
-                beginControlFlow("if (%L == -1)", itemKeyIndexVar).apply {
-                    addStatement("return")
-                }
-                endControlFlow()
+    private fun XCodeBlock.Builder.addRelationCollectorCode(
+        scope: CodeGenScope,
+        cursorVar: String
+    ) {
+        val relation = collector.relation
+        beginControlFlow("try").apply {
+            // Gets index of the column to be used as key
+            val itemKeyIndexVar = "_itemKeyIndex"
+            if (relation.junction != null) {
+                // When using a junction table the relationship map is keyed on the parent
+                // reference column of the junction table, the same column used in the WHERE IN
+                // clause, this column is the rightmost column in the generated SELECT
+                // clause.
+                val junctionParentColumnIndex = relation.projection.size
+                addStatement("// _junction.%L", relation.junction.parentField.columnName)
+                addLocalVal(
+                    itemKeyIndexVar,
+                    XTypeName.PRIMITIVE_INT,
+                    "%L",
+                    junctionParentColumnIndex
+                )
+            } else {
+                addLocalVal(
+                    name = itemKeyIndexVar,
+                    typeName = XTypeName.PRIMITIVE_INT,
+                    assignExprFormat = "%M(%L, %S)",
+                    if (useDriverApi) {
+                        RoomTypeNames.STATEMENT_UTIL.packageMember("getColumnIndex")
+                    } else { RoomMemberNames.CURSOR_UTIL_GET_COLUMN_INDEX },
+                    cursorVar,
+                    relation.entityField.columnName
+                )
+            }
 
-                // Prepare item column indices
-                collector.rowAdapter.onCursorReady(cursorVarName = cursorVar, scope = scope)
+            // Check if index of column is not -1, indicating the column for the key is not in
+            // the result, can happen if the user specified a bad projection in @Relation.
+            beginControlFlow("if (%L == -1)", itemKeyIndexVar).apply {
+                addStatement("return")
+            }
+            endControlFlow()
 
-                val tmpVarName = scope.getTmpVar("_item")
-                beginControlFlow("while (%L.moveToNext())", cursorVar).apply {
-                    // Read key from the cursor, convert row to item and place it on map
-                    collector.readKey(
-                        cursorVarName = cursorVar,
-                        indexVar = itemKeyIndexVar,
-                        keyReader = collector.entityKeyColumnReader,
-                        scope = scope
-                    ) { keyVar ->
-                        if (collector.relationTypeIsCollection) {
-                            val relationVar = scope.getTmpVar("_tmpRelation")
-                            addLocalVal(
-                                relationVar,
-                                collector.relationTypeName.copy(nullable = true),
-                                "%L.get(%L)",
-                                PARAM_MAP_VARIABLE, keyVar
-                            )
-                            beginControlFlow("if (%L != null)", relationVar)
-                            addLocalVariable(tmpVarName, relation.pojoTypeName)
-                            collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
-                            addStatement("%L.add(%L)", relationVar, tmpVarName)
-                            endControlFlow()
-                        } else {
-                            beginControlFlow("if (%N.containsKey(%L))", PARAM_MAP_VARIABLE, keyVar)
-                            addLocalVariable(tmpVarName, relation.pojoTypeName)
-                            collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
-                            addStatement("%N.put(%L, %L)", PARAM_MAP_VARIABLE, keyVar, tmpVarName)
-                            endControlFlow()
-                        }
+            // Prepare item column indices
+            collector.rowAdapter.onCursorReady(cursorVarName = cursorVar, scope = scope)
+            val tmpVarName = scope.getTmpVar("_item")
+            val stepName = if (scope.useDriverApi) "step" else "moveToNext"
+            beginControlFlow("while (%L.$stepName())", cursorVar).apply {
+                // Read key from the cursor, convert row to item and place it on map
+                collector.readKey(
+                    cursorVarName = cursorVar,
+                    indexVar = itemKeyIndexVar,
+                    keyReader = collector.entityKeyColumnReader,
+                    scope = scope
+                ) { keyVar ->
+                    if (collector.relationTypeIsCollection) {
+                        val relationVar = scope.getTmpVar("_tmpRelation")
+                        addLocalVal(
+                            relationVar,
+                            collector.relationTypeName.copy(nullable = true),
+                            "%L.get(%L)",
+                            PARAM_MAP_VARIABLE, keyVar
+                        )
+                        beginControlFlow("if (%L != null)", relationVar)
+                        addLocalVariable(tmpVarName, relation.pojoTypeName)
+                        collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
+                        addStatement("%L.add(%L)", relationVar, tmpVarName)
+                        endControlFlow()
+                    } else {
+                        beginControlFlow("if (%N.containsKey(%L))", PARAM_MAP_VARIABLE, keyVar)
+                        addLocalVariable(tmpVarName, relation.pojoTypeName)
+                        collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
+                        addStatement("%N.put(%L, %L)", PARAM_MAP_VARIABLE, keyVar, tmpVarName)
+                        endControlFlow()
                     }
                 }
-                endControlFlow()
-            }
-            nextControlFlow("finally").apply {
-                addStatement("%L.close()", cursorVar)
             }
             endControlFlow()
         }
-        builder.apply {
-            addParameter(collector.mapTypeName, PARAM_MAP_VARIABLE)
-            addCode(scope.generate())
+        nextControlFlow("finally").apply {
+            addStatement("%L.close()", cursorVar)
         }
+        endControlFlow()
     }
 
     private fun XCodeBlock.Builder.addIsInputEmptyCheck() {
@@ -209,62 +258,56 @@
         endControlFlow()
     }
 
-    private fun XCodeBlock.Builder.addRecursiveFetchCall(methodName: String) {
-        fun getRecursiveCall(itVarName: String) =
-            XCodeBlock.of(
-                language,
-                "%L(%L)",
-                methodName, itVarName
-            )
+    private fun XCodeBlock.Builder.addRecursiveFetchCall(
+        scope: CodeGenScope,
+        methodName: String,
+    ) {
         val utilFunction =
-            RoomTypeNames.RELATION_UTIL.let {
+            RELATION_UTIL.let {
                 when {
                     usingLongSparseArray ->
                         it.packageMember("recursiveFetchLongSparseArray")
                     usingArrayMap ->
                         it.packageMember("recursiveFetchArrayMap")
-                    else ->
-                        it.packageMember("recursiveFetchHashMap")
+                    else -> when (language) {
+                        CodeLanguage.JAVA -> it.packageMember("recursiveFetchHashMap")
+                        CodeLanguage.KOTLIN -> it.packageMember("recursiveFetchMap")
+                    }
                 }
             }
-        when (language) {
-            CodeLanguage.JAVA -> {
-                val paramName = "map"
-                if (collector.javaLambdaSyntaxAvailable) {
-                    add("%M(%L, %L, (%L) -> {\n",
-                        utilFunction, PARAM_MAP_VARIABLE, collector.relationTypeIsCollection,
-                        paramName
-                    )
-                    indent()
-                    addStatement("%L", getRecursiveCall(paramName))
-                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
-                    unindent()
-                    addStatement("})")
-                } else {
-                    val functionImpl = Function1TypeSpec(
-                        language = language,
-                        parameterTypeName = collector.mapTypeName,
-                        parameterName = paramName,
-                        returnTypeName = KotlinTypeNames.UNIT,
-                    ) {
-                        addStatement("%L", getRecursiveCall(paramName))
+        val paramName = scope.getTmpVar("_tmpMap")
+        val recursiveFetchBlock = InvokeWithLambdaParameter(
+            scope = scope,
+            functionName = utilFunction,
+            argFormat = listOf("%L", "%L"),
+            args = listOf(PARAM_MAP_VARIABLE, collector.relationTypeIsCollection),
+            lambdaSpec = object : LambdaSpec(
+                parameterTypeName = collector.mapTypeName,
+                parameterName = paramName,
+                returnTypeName = KotlinTypeNames.UNIT,
+                javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+            ) {
+                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                    val recursiveCall = if (useDriverApi) {
+                        XCodeBlock.of(
+                            language,
+                            "%L(%L, %L)",
+                            methodName, PARAM_CONNECTION_VARIABLE, paramName
+                        )
+                    } else {
+                        XCodeBlock.of(
+                            language,
+                            "%L(%L)",
+                            methodName, paramName
+                        )
+                    }
+                    addStatement("%L", recursiveCall)
+                    if (language == CodeLanguage.JAVA) {
                         addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                     }
-                    addStatement(
-                        "%M(%L, %L, %L)",
-                        utilFunction, PARAM_MAP_VARIABLE, collector.relationTypeIsCollection,
-                        functionImpl
-                    )
                 }
             }
-            CodeLanguage.KOTLIN -> {
-                beginControlFlow(
-                    "%M(%L, %L)",
-                    utilFunction, PARAM_MAP_VARIABLE, collector.relationTypeIsCollection
-                )
-                addStatement("%L", getRecursiveCall("it"))
-                endControlFlow()
-            }
-        }
+        )
+        add("%L", recursiveFetchBlock)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
index 96c64dc..1700a64 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
@@ -36,7 +36,10 @@
 /**
  * Base class for all writers that can produce a class.
  */
-abstract class TypeWriter(val codeLanguage: CodeLanguage) {
+abstract class TypeWriter(
+    val codeLanguage: CodeLanguage,
+    val javaLambdaSyntaxAvailable: Boolean,
+) {
     private val sharedFieldSpecs = mutableMapOf<String, XPropertySpec>()
     private val sharedMethodSpecs = mutableMapOf<String, XFunSpec>()
     private val sharedFieldNames = mutableSetOf<String>()
diff --git a/room/room-compiler/src/main/resources/NOTICE.txt b/room/room-compiler/src/main/resources/META-INF/NOTICE.txt
similarity index 100%
rename from room/room-compiler/src/main/resources/NOTICE.txt
rename to room/room-compiler/src/main/resources/META-INF/NOTICE.txt
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 2f432e3..a680f2d 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
@@ -219,7 +219,7 @@
                 val processed = DaoProcessor(
                     invocation.context, dao, dbType, null
                 ).process()
-                DaoWriter(processed, dbElm, CodeLanguage.JAVA)
+                DaoWriter(processed, dbElm, CodeLanguage.JAVA, false)
                     .write(invocation.processingEnv)
             }
         }
@@ -270,7 +270,8 @@
                 invocation.context, daoElm, dbType, null
             ).process()
             handler(processedDao)
-            DaoWriter(processedDao, dbElm, CodeLanguage.JAVA).write(invocation.processingEnv)
+            DaoWriter(processedDao, dbElm, CodeLanguage.JAVA, false)
+                .write(invocation.processingEnv)
         }
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index bbf992b..028b786 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -335,7 +335,8 @@
                 """
         ) { parsedQuery, invocation ->
             val expected = MUTABLE_LIST.parametrizedBy(
-                XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT))
+                XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT.copy(
+                    nullable = true)))
             )
             assertThat(parsedQuery.returnType.asTypeName(), `is`(expected))
             invocation.assertCompilationResult {
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 997f6d3..d8a127c 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
@@ -402,7 +402,8 @@
                 dao = daoProcessor.process(),
                 dbElement = invocation.processingEnv
                     .requireTypeElement("androidx.room.RoomDatabase"),
-                codeLanguage = CodeLanguage.JAVA
+                codeLanguage = CodeLanguage.JAVA,
+                javaLambdaSyntaxAvailable = false
             ).write(invocation.processingEnv)
             invocation.assertCompilationResult {
                 generatedSourceFileWithPath("MyDao_Impl.java").let {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 5c4d981..2a63f03 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -332,7 +332,7 @@
 
 fun testCodeGenScope(): CodeGenScope {
     return CodeGenScope(
-        object : TypeWriter(CodeLanguage.JAVA) {
+        object : TypeWriter(CodeLanguage.JAVA, true) {
             override fun createTypeSpecBuilder(): XTypeSpec.Builder {
                 return XTypeSpec.classBuilder(codeLanguage, XClassName.get("test", "Foo"))
             }
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 e541095..298b09e 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
@@ -111,9 +111,11 @@
                 isSpecProvided = false
             )
             AutoMigrationWriter(
-                invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
-                autoMigrationResultWithNewAddedColumn,
-                codeLanguage
+                autoMigration = autoMigrationResultWithNewAddedColumn,
+                dbElement =
+                    invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
+                codeLanguage = codeLanguage,
+                javaLambdaSyntaxAvailable = false
             ).write(invocation.processingEnv)
 
             val expectedFile = when (codeLanguage) {
@@ -185,9 +187,11 @@
                 isSpecProvided = false
             )
             AutoMigrationWriter(
-                invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
-                autoMigrationResultWithNewAddedColumn,
-                codeLanguage
+                autoMigration = autoMigrationResultWithNewAddedColumn,
+                dbElement =
+                    invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
+                codeLanguage = codeLanguage,
+                javaLambdaSyntaxAvailable = false
             ).write(invocation.processingEnv)
 
             val expectedFile = when (codeLanguage) {
@@ -267,9 +271,11 @@
                 isSpecProvided = true
             )
             AutoMigrationWriter(
-                invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
-                autoMigrationResultWithNewAddedColumn,
-                codeLanguage
+                autoMigration = autoMigrationResultWithNewAddedColumn,
+                dbElement =
+                    invocation.processingEnv.requireTypeElement("foo.bar.MyDatabase"),
+                codeLanguage = codeLanguage,
+                javaLambdaSyntaxAvailable = false
             ).write(invocation.processingEnv)
 
             val expectedFile = when (codeLanguage) {
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 de81792..7643c91 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
@@ -25,17 +25,19 @@
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.DaoProcessor
 import androidx.room.testing.context
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
 import createVerifierFromEntitiesAndViews
 import java.util.Locale
 import loadTestSource
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import writeTestSource
 
-@RunWith(JUnit4::class)
+@RunWith(TestParameterInjector::class)
 class DaoWriterTest {
     @Test
-    fun complexDao() {
+    fun complexDao(@TestParameter javaLambdaSyntaxAvailable: Boolean) {
         singleDao(
             loadTestSource(
                 fileName = "databasewriter/input/ComplexDatabase.java",
@@ -44,18 +46,10 @@
             loadTestSource(
                 fileName = "daoWriter/input/ComplexDao.java",
                 qName = "foo.bar.ComplexDao"
-            )
-        ) {
-            val backendFolder = backendFolder(it)
-            it.assertCompilationResult {
-                generatedSource(
-                    loadTestSource(
-                        fileName = "daoWriter/output/$backendFolder/ComplexDao.java",
-                        qName = "foo.bar.ComplexDao_Impl"
-                    )
-                )
-            }
-        }
+            ),
+            javaLambdaSyntaxAvailable = javaLambdaSyntaxAvailable,
+            outputFileName = "ComplexDao.java"
+        )
     }
 
     @Test
@@ -63,95 +57,65 @@
         val originalLocale = Locale.getDefault()
         try {
             Locale.setDefault(Locale("tr")) // Turkish has special upper/lowercase i chars
-            complexDao()
+            complexDao(false)
         } finally {
             Locale.setDefault(originalLocale)
         }
     }
 
     @Test
-    fun writerDao() {
+    fun writerDao(@TestParameter javaLambdaSyntaxAvailable: Boolean) {
         singleDao(
             loadTestSource(
                 fileName = "daoWriter/input/WriterDao.java",
                 qName = "foo.bar.WriterDao"
-            )
-        ) {
-            val backendFolder = backendFolder(it)
-            it.assertCompilationResult {
-                generatedSource(
-                    loadTestSource(
-                        fileName = "daoWriter/output/$backendFolder/WriterDao.java",
-                        qName = "foo.bar.WriterDao_Impl"
-                    )
-                )
-            }
-        }
+            ),
+            javaLambdaSyntaxAvailable = javaLambdaSyntaxAvailable,
+            outputFileName = "WriterDao.java"
+        )
     }
 
     @Test
-    fun deletionDao() {
+    fun deletionDao(@TestParameter javaLambdaSyntaxAvailable: Boolean) {
         singleDao(
             loadTestSource(
                 fileName = "daoWriter/input/DeletionDao.java",
                 qName = "foo.bar.DeletionDao"
-            )
-        ) {
-            val backendFolder = backendFolder(it)
-            it.assertCompilationResult {
-                generatedSource(
-                    loadTestSource(
-                        fileName = "daoWriter/output/$backendFolder/DeletionDao.java",
-                        qName = "foo.bar.DeletionDao_Impl"
-                    )
-                )
-            }
-        }
+            ),
+            javaLambdaSyntaxAvailable = javaLambdaSyntaxAvailable,
+            outputFileName = "DeletionDao.java"
+        )
     }
 
     @Test
-    fun updateDao() {
+    fun updateDao(@TestParameter javaLambdaSyntaxAvailable: Boolean) {
         singleDao(
             loadTestSource(
                 fileName = "daoWriter/input/UpdateDao.java",
                 qName = "foo.bar.UpdateDao"
-            )
-        ) {
-            val backendFolder = backendFolder(it)
-            it.assertCompilationResult {
-                generatedSource(
-                    loadTestSource(
-                        fileName = "daoWriter/output/$backendFolder/UpdateDao.java",
-                        qName = "foo.bar.UpdateDao_Impl"
-                    )
-                )
-            }
-        }
+            ),
+            javaLambdaSyntaxAvailable = javaLambdaSyntaxAvailable,
+            outputFileName = "UpdateDao.java"
+        )
     }
 
     @Test
-    fun upsertDao() {
+    fun upsertDao(@TestParameter javaLambdaSyntaxAvailable: Boolean) {
         singleDao(
             loadTestSource(
                 fileName = "daoWriter/input/UpsertDao.java",
                 qName = "foo.bar.UpsertDao"
-            )
-        ) {
-            val backendFolder = backendFolder(it)
-            it.assertCompilationResult {
-                generatedSource(
-                    loadTestSource(
-                        fileName = "daoWriter/output/$backendFolder/UpsertDao.java",
-                        qName = "foo.bar.UpsertDao_Impl"
-                    )
-                )
-            }
-        }
+            ),
+            javaLambdaSyntaxAvailable = javaLambdaSyntaxAvailable,
+            outputFileName = "UpsertDao.java"
+        )
     }
 
     private fun singleDao(
         vararg inputs: Source,
-        handler: (XTestInvocation) -> Unit
+        javaLambdaSyntaxAvailable: Boolean = false,
+        outputFileName: String,
+        handler: (XTestInvocation) -> Unit = { }
     ) {
         val sources = listOf(
             COMMON.USER, COMMON.MULTI_PKEY_ENTITY, COMMON.BOOK,
@@ -165,6 +129,10 @@
         runProcessorTest(
             sources = sources
         ) { invocation ->
+            if (invocation.isKsp && !javaLambdaSyntaxAvailable) {
+                // Skip KSP backend without lambda syntax, it is a nonsensical combination.
+                return@runProcessorTest
+            }
             val dao = invocation.roundEnv
                 .getElementsAnnotatedWith(
                     androidx.room.Dao::class.qualifiedName!!
@@ -186,15 +154,42 @@
                     dbVerifier = dbVerifier
                 )
                 val parsedDao = parser.process()
-                DaoWriter(parsedDao, db, CodeLanguage.JAVA)
+                DaoWriter(parsedDao, db, CodeLanguage.JAVA, javaLambdaSyntaxAvailable)
                     .write(invocation.processingEnv)
+                val outputSubFolder = outputFolder(invocation, javaLambdaSyntaxAvailable)
+                invocation.assertCompilationResult {
+                    val expectedFilePath = "daoWriter/output/$outputSubFolder/$outputFileName"
+                    val expectedSrc = loadTestSource(
+                        fileName = expectedFilePath,
+                        qName = parsedDao.implTypeName.canonicalName
+                    )
+                    // Set ROOM_TEST_WRITE_SRCS env variable to make tests write expected sources,
+                    // handy for big sweeping code gen changes. ;)
+                    if (System.getenv("ROOM_TEST_WRITE_SRCS") != null) {
+                        writeTestSource(
+                            checkNotNull(this.findGeneratedSource(expectedSrc.relativePath)) {
+                                "Couldn't find gen src: $expectedSrc"
+                            },
+                            expectedFilePath
+                        )
+                    }
+                    generatedSource(expectedSrc)
+                }
             }
-            // we could call handler inside the if block but if something happens and we cannot
-            // find the dao, test will never assert on generated code.
             handler(invocation)
         }
     }
 
-    private fun backendFolder(invocation: XTestInvocation) =
-        invocation.processingEnv.backend.name.lowercase()
+    private fun outputFolder(
+        invocation: XTestInvocation,
+        javaLambdaSyntaxAvailable: Boolean
+    ): String {
+        val backendFolder = invocation.processingEnv.backend.name.lowercase()
+        val lambdaFolder = if (javaLambdaSyntaxAvailable) "withLambda" else "withoutLambda"
+        if (invocation.isKsp) {
+            return backendFolder
+        } else {
+            return "$backendFolder/$lambdaFolder"
+        }
+    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
index 1ca847b..5600862 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
@@ -195,7 +195,7 @@
                         dbVerifier = createVerifierFromEntitiesAndViews(invocation)
                     )
                     val parsedDao = parser.process()
-                    DaoWriter(parsedDao, db, CodeLanguage.JAVA)
+                    DaoWriter(parsedDao, db, CodeLanguage.JAVA, true)
                         .write(invocation.processingEnv)
                     invocation.assertCompilationResult {
                         val relativePath =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt
index b0e8158..76d5b76 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt
@@ -122,7 +122,7 @@
     ) {
         singleEntity(input) { entity, invocation ->
             val className = XClassName.get("foo.bar", "MyContainerClass")
-            val writer = object : TypeWriter(CodeLanguage.JAVA) {
+            val writer = object : TypeWriter(CodeLanguage.JAVA, true) {
                 override fun createTypeSpecBuilder(): XTypeSpec.Builder {
                     getOrCreateFunction(EntityCursorConverterWriter(entity))
                     return XTypeSpec.classBuilder(codeLanguage, className)
diff --git a/room/room-compiler/src/test/test-data/daoWriter/input/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/input/UpdateDao.java
index 226dccc..317c7cc 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/input/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/input/UpdateDao.java
@@ -69,10 +69,4 @@
 
     @Query("UPDATE User SET ageColumn = ageColumn + 1")
     Maybe<Integer> ageUserAllMaybe();
-
-    @Transaction
-    default void updateAndAge(User user) {
-        updateUser(user);
-        ageUserByUid(String.valueOf(user.uid));
-    }
 }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
deleted file mode 100644
index b29e9ef..0000000
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
+++ /dev/null
@@ -1,759 +0,0 @@
-package foo.bar;
-
-import android.database.Cursor;
-import android.os.CancellationSignal;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
-import androidx.room.guava.GuavaRoom;
-import androidx.room.util.CursorUtil;
-import androidx.room.util.DBUtil;
-import androidx.room.util.SQLiteStatementUtil;
-import androidx.room.util.StringUtil;
-import androidx.sqlite.SQLiteConnection;
-import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteQuery;
-import com.google.common.util.concurrent.ListenableFuture;
-import java.lang.Class;
-import java.lang.Exception;
-import java.lang.Integer;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.StringBuilder;
-import java.lang.SuppressWarnings;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
-
-@Generated("androidx.room.RoomProcessor")
-@SuppressWarnings({"unchecked", "deprecation", "removal"})
-public final class ComplexDao_Impl extends ComplexDao {
-    private final RoomDatabase __db;
-
-    public ComplexDao_Impl(final ComplexDatabase __db) {
-        super(__db);
-        this.__db = __db;
-    }
-
-    @Override
-    public boolean transactionMethod(final int i, final String s, final long l) {
-        return DBUtil.performBlocking(__db, false, true, (_connection) -> {
-            return ComplexDao_Impl.super.transactionMethod(i, s, l);
-        });
-    }
-
-    @Override
-    public List<ComplexDao.FullName> fullNames(final int id) {
-        final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<ComplexDao.FullName>>() {
-            @Override
-            @NonNull
-            public List<ComplexDao.FullName> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _cursorIndexOfFullName = 0;
-                    final int _cursorIndexOfId = 1;
-                    final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
-                    while (_stmt.step()) {
-                        final ComplexDao.FullName _item;
-                        _item = new ComplexDao.FullName();
-                        if (_stmt.isNull(_cursorIndexOfFullName)) {
-                            _item.fullName = null;
-                        } else {
-                            _item.fullName = _stmt.getText(_cursorIndexOfFullName);
-                        }
-                        _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User getById(final int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
-            @Override
-            @NonNull
-            public User invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final User _result;
-                    if (_stmt.step()) {
-                        _result = new User();
-                        _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _result.name = null;
-                        } else {
-                            _result.name = _stmt.getText(_cursorIndexOfName);
-                        }
-                        final String _tmpLastName;
-                        if (_stmt.isNull(_cursorIndexOfLastName)) {
-                            _tmpLastName = null;
-                        } else {
-                            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        }
-                        _result.setLastName(_tmpLastName);
-                        _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User findByName(final String name, final String lastName) {
-        final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
-            @Override
-            @NonNull
-            public User invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (name == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        _stmt.bindText(_argIndex, name);
-                    }
-                    _argIndex = 2;
-                    if (lastName == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        _stmt.bindText(_argIndex, lastName);
-                    }
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final User _result;
-                    if (_stmt.step()) {
-                        _result = new User();
-                        _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _result.name = null;
-                        } else {
-                            _result.name = _stmt.getText(_cursorIndexOfName);
-                        }
-                        final String _tmpLastName;
-                        if (_stmt.isNull(_cursorIndexOfLastName)) {
-                            _tmpLastName = null;
-                        } else {
-                            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        }
-                        _result.setLastName(_tmpLastName);
-                        _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<User> loadAllByIds(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<User>>() {
-            @Override
-            @NonNull
-            public List<User> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : ids) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final List<User> _result = new ArrayList<User>();
-                    while (_stmt.step()) {
-                        final User _item_1;
-                        _item_1 = new User();
-                        _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _item_1.name = null;
-                        } else {
-                            _item_1.name = _stmt.getText(_cursorIndexOfName);
-                        }
-                        final String _tmpLastName;
-                        if (_stmt.isNull(_cursorIndexOfLastName)) {
-                            _tmpLastName = null;
-                        } else {
-                            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        }
-                        _item_1.setLastName(_tmpLastName);
-                        _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    int getAge(final int id) {
-        final String _sql = "SELECT ageColumn FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _result;
-                    if (_stmt.step()) {
-                        _result = (int) (_stmt.getLong(0));
-                    } else {
-                        _result = 0;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int[] getAllAges(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, int[]>() {
-            @Override
-            @NonNull
-            public int[] invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : ids) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _listResult = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_1;
-                        if (_stmt.isNull(0)) {
-                            _item_1 = null;
-                        } else {
-                            _item_1 = (int) (_stmt.getLong(0));
-                        }
-                        _listResult.add(_item_1);
-                    }
-                    final int[] _tmpArrayResult = new int[_listResult.size()];
-                    int _index = 0;
-                    for (int _listItem : _listResult) {
-                        _tmpArrayResult[_index] = _listItem;
-                        _index++;
-                    }
-                    final int[] _result = _tmpArrayResult;
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<Integer> getAllAgesAsList(final List<Integer> ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids == null ? 1 : ids.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
-            @Override
-            @NonNull
-            public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (Integer _item : ids) {
-                            if (_item == null) {
-                                _stmt.bindNull(_argIndex);
-                            } else {
-                                _stmt.bindLong(_argIndex, _item);
-                            }
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _result = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_1;
-                        if (_stmt.isNull(0)) {
-                            _item_1 = null;
-                        } else {
-                            _item_1 = (int) (_stmt.getLong(0));
-                        }
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
-            final int... ids3) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids1 == null ? 1 : ids1.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_1 = ids2 == null ? 1 : ids2.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_2 = ids3 == null ? 1 : ids3.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
-            @Override
-            @NonNull
-            public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids1 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (Integer _item : ids1) {
-                            if (_item == null) {
-                                _stmt.bindNull(_argIndex);
-                            } else {
-                                _stmt.bindLong(_argIndex, _item);
-                            }
-                            _argIndex++;
-                        }
-                    }
-                    _argIndex = 1 + _inputSize;
-                    if (ids2 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item_1 : ids2) {
-                            _stmt.bindLong(_argIndex, _item_1);
-                            _argIndex++;
-                        }
-                    }
-                    _argIndex = 1 + _inputSize + _inputSize_1;
-                    if (ids3 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item_2 : ids3) {
-                            _stmt.bindLong(_argIndex, _item_2);
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _result = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_3;
-                        if (_stmt.isNull(0)) {
-                            _item_3 = null;
-                        } else {
-                            _item_3 = (int) (_stmt.getLong(0));
-                        }
-                        _result.add(_item_3);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public LiveData<User> getByIdLive(final int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-        int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
-            @Override
-            @Nullable
-            public User call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-                try {
-                    final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-                    final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-                    final User _result;
-                    if (_cursor.moveToFirst()) {
-                        _result = new User();
-                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
-                        if (_cursor.isNull(_cursorIndexOfName)) {
-                            _result.name = null;
-                        } else {
-                            _result.name = _cursor.getString(_cursorIndexOfName);
-                        }
-                        final String _tmpLastName;
-                        if (_cursor.isNull(_cursorIndexOfLastName)) {
-                            _tmpLastName = null;
-                        } else {
-                            _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        }
-                        _result.setLastName(_tmpLastName);
-                        _result.age = _cursor.getInt(_cursorIndexOfAge);
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
-
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        });
-    }
-
-    @Override
-    public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        if (ids == null) {
-            _statement.bindNull(_argIndex);
-        } else {
-            for (int _item : ids) {
-                _statement.bindLong(_argIndex, _item);
-                _argIndex++;
-            }
-        }
-        return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
-            @Override
-            @Nullable
-            public List<User> call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-                try {
-                    final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-                    final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-                    final List<User> _result = new ArrayList<User>();
-                    while (_cursor.moveToNext()) {
-                        final User _item_1;
-                        _item_1 = new User();
-                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-                        if (_cursor.isNull(_cursorIndexOfName)) {
-                            _item_1.name = null;
-                        } else {
-                            _item_1.name = _cursor.getString(_cursorIndexOfName);
-                        }
-                        final String _tmpLastName;
-                        if (_cursor.isNull(_cursorIndexOfLastName)) {
-                            _tmpLastName = null;
-                        } else {
-                            _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        }
-                        _item_1.setLastName(_tmpLastName);
-                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
-
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        });
-    }
-
-    @Override
-    public List<Child1> getChild1List() {
-        final String _sql = "SELECT * FROM Child1";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
-            @Override
-            @NonNull
-            public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
-                    final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
-                    final List<Child1> _result = new ArrayList<Child1>();
-                    while (_stmt.step()) {
-                        final Child1 _item;
-                        final int _tmpId;
-                        _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
-                        final String _tmpName;
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _tmpName = null;
-                        } else {
-                            _tmpName = _stmt.getText(_cursorIndexOfName);
-                        }
-                        final Info _tmpInfo;
-                        if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
-                            if (_stmt.isNull(_cursorIndexOfCode)) {
-                                _tmpInfo.code = null;
-                            } else {
-                                _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
-                            }
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<Child2> getChild2List() {
-        final String _sql = "SELECT * FROM Child2";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child2>>() {
-            @Override
-            @NonNull
-            public List<Child2> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
-                    final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
-                    final List<Child2> _result = new ArrayList<Child2>();
-                    while (_stmt.step()) {
-                        final Child2 _item;
-                        final int _tmpId;
-                        _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
-                        final String _tmpName;
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _tmpName = null;
-                        } else {
-                            _tmpName = _stmt.getText(_cursorIndexOfName);
-                        }
-                        final Info _tmpInfo;
-                        if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
-                            if (_stmt.isNull(_cursorIndexOfCode)) {
-                                _tmpInfo.code = null;
-                            } else {
-                                _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
-                            }
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child2(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<List<Child1>> getChild1ListListenableFuture() {
-        final String _sql = "SELECT * FROM Child1";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
-        final CancellationSignal _cancellationSignal = new CancellationSignal();
-        return GuavaRoom.createListenableFuture(__db, false, new Callable<List<Child1>>() {
-            @Override
-            public List<Child1> call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, _cancellationSignal);
-                try {
-                    final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
-                    final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
-                    final List<Child1> _result = new ArrayList<Child1>();
-                    while (_cursor.moveToNext()) {
-                        final Child1 _item;
-                        final int _tmpId;
-                        _tmpId = _cursor.getInt(_cursorIndexOfId);
-                        final String _tmpName;
-                        if (_cursor.isNull(_cursorIndexOfName)) {
-                            _tmpName = null;
-                        } else {
-                            _tmpName = _cursor.getString(_cursorIndexOfName);
-                        }
-                        final Info _tmpInfo;
-                        if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
-                            if (_cursor.isNull(_cursorIndexOfCode)) {
-                                _tmpInfo.code = null;
-                            } else {
-                                _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
-                            }
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
-        }, _statement, true, _cancellationSignal);
-    }
-
-    @Override
-    public List<UserSummary> getUserNames() {
-        final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<UserSummary>>() {
-            @Override
-            @NonNull
-            public List<UserSummary> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfUid = 0;
-                    final int _cursorIndexOfName = 1;
-                    final List<UserSummary> _result = new ArrayList<UserSummary>();
-                    while (_stmt.step()) {
-                        final UserSummary _item;
-                        _item = new UserSummary();
-                        _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        if (_stmt.isNull(_cursorIndexOfName)) {
-                            _item.name = null;
-                        } else {
-                            _item.name = _stmt.getText(_cursorIndexOfName);
-                        }
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User getUserViaRawQuery(final SupportSQLiteQuery rawQuery) {
-        __db.assertNotSuspendingTransaction();
-        final Cursor _cursor = DBUtil.query(__db, rawQuery, false, null);
-        try {
-            final User _result;
-            if (_cursor.moveToFirst()) {
-                _result = __entityCursorConverter_fooBarUser(_cursor);
-            } else {
-                _result = null;
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-        }
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-
-    private User __entityCursorConverter_fooBarUser(@NonNull final Cursor cursor) {
-        final User _entity;
-        final int _cursorIndexOfUid = CursorUtil.getColumnIndex(cursor, "uid");
-        final int _cursorIndexOfName = CursorUtil.getColumnIndex(cursor, "name");
-        final int _cursorIndexOfLastName = CursorUtil.getColumnIndex(cursor, "lastName");
-        final int _cursorIndexOfAge = CursorUtil.getColumnIndex(cursor, "ageColumn");
-        _entity = new User();
-        if (_cursorIndexOfUid != -1) {
-            _entity.uid = cursor.getInt(_cursorIndexOfUid);
-        }
-        if (_cursorIndexOfName != -1) {
-            if (cursor.isNull(_cursorIndexOfName)) {
-                _entity.name = null;
-            } else {
-                _entity.name = cursor.getString(_cursorIndexOfName);
-            }
-        }
-        if (_cursorIndexOfLastName != -1) {
-            final String _tmpLastName;
-            if (cursor.isNull(_cursorIndexOfLastName)) {
-                _tmpLastName = null;
-            } else {
-                _tmpLastName = cursor.getString(_cursorIndexOfLastName);
-            }
-            _entity.setLastName(_tmpLastName);
-        }
-        if (_cursorIndexOfAge != -1) {
-            _entity.age = cursor.getInt(_cursorIndexOfAge);
-        }
-        return _entity;
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
deleted file mode 100644
index c773530..0000000
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
+++ /dev/null
@@ -1,417 +0,0 @@
-package foo.bar;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
-import androidx.room.RoomDatabase;
-import androidx.room.util.DBUtil;
-import androidx.room.util.SQLiteConnectionUtil;
-import androidx.room.util.StringUtil;
-import androidx.sqlite.SQLiteConnection;
-import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
-import io.reactivex.Completable;
-import io.reactivex.Maybe;
-import io.reactivex.Single;
-import java.lang.Class;
-import java.lang.Exception;
-import java.lang.Integer;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.StringBuilder;
-import java.lang.SuppressWarnings;
-import java.lang.Void;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
-
-@Generated("androidx.room.RoomProcessor")
-@SuppressWarnings({"unchecked", "deprecation", "removal"})
-public final class DeletionDao_Impl implements DeletionDao {
-    private final RoomDatabase __db;
-
-    private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
-
-    private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
-    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
-
-    private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
-
-    public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `User` WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-            }
-        };
-        this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `User` WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-            }
-        };
-        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
-                if (entity.name == null) {
-                    statement.bindNull(1);
-                } else {
-                    statement.bindText(1, entity.name);
-                }
-                if (entity.lastName == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.lastName);
-                }
-            }
-        };
-        this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `Book` WHERE `bookId` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
-                statement.bindLong(1, entity.bookId);
-            }
-        };
-    }
-
-    @Override
-    public void deleteUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void deleteUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user1);
-                __deleteAdapterOfUser.handleMultiple(_connection, others);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void deleteArrayOfUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handleMultiple(_connection, users);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public Integer deleteUserAndReturnCountObject(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public int deleteUserAndReturnCount(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public int deleteUserAndReturnCount(final User user1, final List<User> others) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user1);
-                _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public int deleteUserAndReturnCount(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public Completable deleteUserCompletable(final User user) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                __db.beginTransaction();
-                try {
-                    __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Single<Integer> deleteUserSingle(final User user) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Maybe<Integer> deleteUserMaybe(final User user) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int multiPKey(final MultiPKeyEntity entity) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public void deleteUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user);
-                __deleteAdapterOfBook.handle(_connection, book);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public int deleteByUid(final int uid) {
-        final String _sql = "DELETE FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, uid);
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Completable deleteByUidCompletable(final int uid) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Single<Integer> deleteByUidSingle(final int uid) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Maybe<Integer> deleteByUidMaybe(final int uid) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int deleteByUidList(final int... uid) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("DELETE FROM user where uid IN(");
-        final int _inputSize = uid == null ? 1 : uid.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (uid == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : uid) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int deleteEverything() {
-        final String _sql = "DELETE FROM user";
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
deleted file mode 100644
index 39eb440..0000000
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
+++ /dev/null
@@ -1,464 +0,0 @@
-package foo.bar;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
-import androidx.room.RoomDatabase;
-import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
-import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
-import io.reactivex.Completable;
-import io.reactivex.Maybe;
-import io.reactivex.Single;
-import java.lang.Class;
-import java.lang.Exception;
-import java.lang.Integer;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.SuppressWarnings;
-import java.lang.Void;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-import javax.annotation.processing.Generated;
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
-
-@Generated("androidx.room.RoomProcessor")
-@SuppressWarnings({"unchecked", "deprecation", "removal"})
-public final class UpdateDao_Impl implements UpdateDao {
-    private final RoomDatabase __db;
-
-    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
-
-    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
-
-    private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
-    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
-
-    private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
-
-    public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindString(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindString(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
-                if (entity.name == null) {
-                    statement.bindNull(1);
-                } else {
-                    statement.bindText(1, entity.name);
-                }
-                if (entity.lastName == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.lastName);
-                }
-                if (entity.name == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.name);
-                }
-                if (entity.lastName == null) {
-                    statement.bindNull(4);
-                } else {
-                    statement.bindText(4, entity.lastName);
-                }
-            }
-        };
-        this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-                statement.bindLong(3, entity.bookId);
-            }
-        };
-    }
-
-    @Override
-    public void updateUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void updateUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user1);
-                __updateAdapterOfUser.handleMultiple(_connection, others);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void updateArrayOfUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handleMultiple(_connection, users);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void updateTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser_1.handle(_connection, userOne);
-                __updateAdapterOfUser_1.handle(_connection, userTwo);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public int updateUserAndReturnCount(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public int updateUserAndReturnCount(final User user1, final List<User> others) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user1);
-                _result += __updateAdapterOfUser.handleMultiple(_connection, others);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public int updateUserAndReturnCount(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handleMultiple(_connection, users);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public Integer updateUserAndReturnCountObject(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public Completable updateUserAndReturnCountCompletable(final User user) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                __db.beginTransaction();
-                try {
-                    __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Single<Integer> updateUserAndReturnCountSingle(final User user) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int multiPKey(final MultiPKeyEntity entity) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
-                return _result;
-            }
-        });
-    }
-
-    @Override
-    public void updateUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user);
-                __updateAdapterOfBook.handle(_connection, book);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void updateAndAge(final User user) {
-        DBUtil.performBlocking(__db, false, true, (_connection) -> {
-            UpdateDao.super.updateAndAge(user);
-            return Unit.INSTANCE;
-        });
-    }
-
-    @Override
-    public void ageUserByUid(final String uid) {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (uid == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        _stmt.bindText(_argIndex, uid);
-                    }
-                    _stmt.step();
-                    return null;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public void ageUserAll() {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    _stmt.step();
-                    return null;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Completable ageUserAllCompletable() {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Single<Integer> ageUserAllSingle() {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Maybe<Integer> ageUserAllMaybe() {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
deleted file mode 100644
index 89ad4f4..0000000
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package foo.bar;
-
-import androidx.annotation.NonNull;
-import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityInsertAdapter;
-import androidx.room.EntityUpsertAdapter;
-import androidx.room.RoomDatabase;
-import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
-import androidx.sqlite.SQLiteStatement;
-import java.lang.Class;
-import java.lang.Long;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.SuppressWarnings;
-import java.lang.Void;
-import java.util.Collections;
-import java.util.List;
-import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
-
-@Generated("androidx.room.RoomProcessor")
-@SuppressWarnings({"unchecked", "deprecation", "removal"})
-public final class UpsertDao_Impl implements UpsertDao {
-    private final RoomDatabase __db;
-
-    private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
-
-    private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
-
-    public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-            }
-        }, new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        });
-        this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-            }
-        }, new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-                statement.bindLong(3, entity.bookId);
-            }
-        });
-    }
-
-    @Override
-    public void upsertUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void upsertUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user1);
-                __upsertAdapterOfUser.upsert(_connection, others);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void upsertUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, users);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void upsertTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, userOne);
-                __upsertAdapterOfUser.upsert(_connection, userTwo);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void upsertUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user);
-                __upsertAdapterOfBook.upsert(_connection, book);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public long upsertAndReturnId(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Long>() {
-            @Override
-            @NonNull
-            public Long invoke(@NonNull final SQLiteConnection _connection) {
-                return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
-            }
-        });
-    }
-
-    @Override
-    public long[] upsertAndReturnIdsArray(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, long[]>() {
-            @Override
-            @NonNull
-            public long[] invoke(@NonNull final SQLiteConnection _connection) {
-                return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
deleted file mode 100644
index 6bfe333..0000000
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package foo.bar;
-
-import androidx.annotation.NonNull;
-import androidx.room.EntityInsertAdapter;
-import androidx.room.RoomDatabase;
-import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
-import androidx.sqlite.SQLiteStatement;
-import java.lang.Class;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.SuppressWarnings;
-import java.lang.Void;
-import java.util.Collections;
-import java.util.List;
-import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
-
-@Generated("androidx.room.RoomProcessor")
-@SuppressWarnings({"unchecked", "deprecation", "removal"})
-public final class WriterDao_Impl implements WriterDao {
-    private final RoomDatabase __db;
-
-    private final EntityInsertAdapter<User> __insertAdapterOfUser;
-
-    private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
-
-    private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
-
-    private final EntityInsertAdapter<Book> __insertAdapterOfBook;
-
-    public WriterDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR ABORT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR REPLACE INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindText(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindText(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR ABORT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
-            }
-
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-            }
-        };
-    }
-
-    @Override
-    public void insertUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void insertUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user1);
-                __insertAdapterOfUser.insert(_connection, others);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void insertUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser_1.insert(_connection, users);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void insertTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser_2.insert(_connection, userOne);
-                __insertAdapterOfUser_2.insert(_connection, userTwo);
-                return null;
-            }
-        });
-    }
-
-    @Override
-    public void insertUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user);
-                __insertAdapterOfBook.insert(_connection, book);
-                return null;
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
new file mode 100644
index 0000000..064c6a6
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
@@ -0,0 +1,713 @@
+package foo.bar;
+
+import android.database.Cursor;
+import android.os.CancellationSignal;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomSQLiteQuery;
+import androidx.room.guava.GuavaRoom;
+import androidx.room.util.CursorUtil;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteStatementUtil;
+import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.lang.SuppressWarnings;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class ComplexDao_Impl extends ComplexDao {
+  private final RoomDatabase __db;
+
+  public ComplexDao_Impl(final ComplexDatabase __db) {
+    super(__db);
+    this.__db = __db;
+  }
+
+  @Override
+  public boolean transactionMethod(final int i, final String s, final long l) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return ComplexDao_Impl.super.transactionMethod(i, s, l);
+    });
+  }
+
+  @Override
+  public List<ComplexDao.FullName> fullNames(final int id) {
+    final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfFullName = 0;
+        final int _cursorIndexOfId = 1;
+        final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
+        while (_stmt.step()) {
+          final ComplexDao.FullName _item;
+          _item = new ComplexDao.FullName();
+          if (_stmt.isNull(_cursorIndexOfFullName)) {
+            _item.fullName = null;
+          } else {
+            _item.fullName = _stmt.getText(_cursorIndexOfFullName);
+          }
+          _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public User getById(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _result.name = null;
+          } else {
+            _result.name = _stmt.getText(_cursorIndexOfName);
+          }
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public User findByName(final String name, final String lastName) {
+    final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (name == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          _stmt.bindText(_argIndex, name);
+        }
+        _argIndex = 2;
+        if (lastName == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          _stmt.bindText(_argIndex, lastName);
+        }
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _result.name = null;
+          } else {
+            _result.name = _stmt.getText(_cursorIndexOfName);
+          }
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<User> loadAllByIds(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
+        }
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final List<User> _result = new ArrayList<User>();
+        while (_stmt.step()) {
+          final User _item_1;
+          _item_1 = new User();
+          _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _item_1.name = null;
+          } else {
+            _item_1.name = _stmt.getText(_cursorIndexOfName);
+          }
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _item_1.setLastName(_tmpLastName);
+          _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  int getAge(final int id) {
+    final String _sql = "SELECT ageColumn FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _result;
+        if (_stmt.step()) {
+          _result = (int) (_stmt.getLong(0));
+        } else {
+          _result = 0;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public int[] getAllAges(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
+        }
+        final List<Integer> _listResult = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_1;
+          if (_stmt.isNull(0)) {
+            _item_1 = null;
+          } else {
+            _item_1 = (int) (_stmt.getLong(0));
+          }
+          _listResult.add(_item_1);
+        }
+        final int[] _tmpArrayResult = new int[_listResult.size()];
+        int _index = 0;
+        for (int _listItem : _listResult) {
+          _tmpArrayResult[_index] = _listItem;
+          _index++;
+        }
+        final int[] _result = _tmpArrayResult;
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (Integer _item : ids) {
+            if (_item == null) {
+              _stmt.bindNull(_argIndex);
+            } else {
+              _stmt.bindLong(_argIndex, _item);
+            }
+            _argIndex++;
+          }
+        }
+        final List<Integer> _result = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_1;
+          if (_stmt.isNull(0)) {
+            _item_1 = null;
+          } else {
+            _item_1 = (int) (_stmt.getLong(0));
+          }
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
+      final int... ids3) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids1 == null ? 1 : ids1.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_1 = ids2 == null ? 1 : ids2.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_2 = ids3 == null ? 1 : ids3.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids1 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (Integer _item : ids1) {
+            if (_item == null) {
+              _stmt.bindNull(_argIndex);
+            } else {
+              _stmt.bindLong(_argIndex, _item);
+            }
+            _argIndex++;
+          }
+        }
+        _argIndex = 1 + _inputSize;
+        if (ids2 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item_1 : ids2) {
+            _stmt.bindLong(_argIndex, _item_1);
+            _argIndex++;
+          }
+        }
+        _argIndex = 1 + _inputSize + _inputSize_1;
+        if (ids3 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item_2 : ids3) {
+            _stmt.bindLong(_argIndex, _item_2);
+            _argIndex++;
+          }
+        }
+        final List<Integer> _result = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_3;
+          if (_stmt.isNull(0)) {
+            _item_3 = null;
+          } else {
+            _item_3 = (int) (_stmt.getLong(0));
+          }
+          _result.add(_item_3);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public LiveData<User> getByIdLive(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+    int _argIndex = 1;
+    _statement.bindLong(_argIndex, id);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
+      @Override
+      @Nullable
+      public User call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final User _result;
+          if (_cursor.moveToFirst()) {
+            _result = new User();
+            _result.uid = _cursor.getInt(_cursorIndexOfUid);
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _result.name = null;
+            } else {
+              _result.name = _cursor.getString(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_cursor.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            }
+            _result.setLastName(_tmpLastName);
+            _result.age = _cursor.getInt(_cursorIndexOfAge);
+          } else {
+            _result = null;
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    final int _argCount = 0 + _inputSize;
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+    int _argIndex = 1;
+    if (ids == null) {
+      _statement.bindNull(_argIndex);
+    } else {
+      for (int _item : ids) {
+        _statement.bindLong(_argIndex, _item);
+        _argIndex++;
+      }
+    }
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
+      @Override
+      @Nullable
+      public List<User> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final List<User> _result = new ArrayList<User>();
+          while (_cursor.moveToNext()) {
+            final User _item_1;
+            _item_1 = new User();
+            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _item_1.name = null;
+            } else {
+              _item_1.name = _cursor.getString(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_cursor.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            }
+            _item_1.setLastName(_tmpLastName);
+            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+            _result.add(_item_1);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public List<Child1> getChild1List() {
+    final String _sql = "SELECT * FROM Child1";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+        final List<Child1> _result = new ArrayList<Child1>();
+        while (_stmt.step()) {
+          final Child1 _item;
+          final int _tmpId;
+          _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+          final String _tmpName;
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _tmpName = null;
+          } else {
+            _tmpName = _stmt.getText(_cursorIndexOfName);
+          }
+          final Info _tmpInfo;
+          if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+            _tmpInfo = new Info();
+            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+            if (_stmt.isNull(_cursorIndexOfCode)) {
+              _tmpInfo.code = null;
+            } else {
+              _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+            }
+          } else {
+            _tmpInfo = null;
+          }
+          _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<Child2> getChild2List() {
+    final String _sql = "SELECT * FROM Child2";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+        final List<Child2> _result = new ArrayList<Child2>();
+        while (_stmt.step()) {
+          final Child2 _item;
+          final int _tmpId;
+          _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+          final String _tmpName;
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _tmpName = null;
+          } else {
+            _tmpName = _stmt.getText(_cursorIndexOfName);
+          }
+          final Info _tmpInfo;
+          if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+            _tmpInfo = new Info();
+            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+            if (_stmt.isNull(_cursorIndexOfCode)) {
+              _tmpInfo.code = null;
+            } else {
+              _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+            }
+          } else {
+            _tmpInfo = null;
+          }
+          _item = new Child2(_tmpId,_tmpName,_tmpInfo);
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public ListenableFuture<List<Child1>> getChild1ListListenableFuture() {
+    final String _sql = "SELECT * FROM Child1";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
+    final CancellationSignal _cancellationSignal = new CancellationSignal();
+    return GuavaRoom.createListenableFuture(__db, false, new Callable<List<Child1>>() {
+      @Override
+      public List<Child1> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, _cancellationSignal);
+        try {
+          final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
+          final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
+          final List<Child1> _result = new ArrayList<Child1>();
+          while (_cursor.moveToNext()) {
+            final Child1 _item;
+            final int _tmpId;
+            _tmpId = _cursor.getInt(_cursorIndexOfId);
+            final String _tmpName;
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _tmpName = null;
+            } else {
+              _tmpName = _cursor.getString(_cursorIndexOfName);
+            }
+            final Info _tmpInfo;
+            if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
+              _tmpInfo = new Info();
+              _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
+              if (_cursor.isNull(_cursorIndexOfCode)) {
+                _tmpInfo.code = null;
+              } else {
+                _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
+              }
+            } else {
+              _tmpInfo = null;
+            }
+            _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+    }, _statement, true, _cancellationSignal);
+  }
+
+  @Override
+  public List<UserSummary> getUserNames() {
+    final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfUid = 0;
+        final int _cursorIndexOfName = 1;
+        final List<UserSummary> _result = new ArrayList<UserSummary>();
+        while (_stmt.step()) {
+          final UserSummary _item;
+          _item = new UserSummary();
+          _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _item.name = null;
+          } else {
+            _item.name = _stmt.getText(_cursorIndexOfName);
+          }
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public User getUserViaRawQuery(final SupportSQLiteQuery rawQuery) {
+    __db.assertNotSuspendingTransaction();
+    final Cursor _cursor = DBUtil.query(__db, rawQuery, false, null);
+    try {
+      final User _result;
+      if (_cursor.moveToFirst()) {
+        _result = __entityCursorConverter_fooBarUser(_cursor);
+      } else {
+        _result = null;
+      }
+      return _result;
+    } finally {
+      _cursor.close();
+    }
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+
+  private User __entityCursorConverter_fooBarUser(@NonNull final Cursor cursor) {
+    final User _entity;
+    final int _cursorIndexOfUid = CursorUtil.getColumnIndex(cursor, "uid");
+    final int _cursorIndexOfName = CursorUtil.getColumnIndex(cursor, "name");
+    final int _cursorIndexOfLastName = CursorUtil.getColumnIndex(cursor, "lastName");
+    final int _cursorIndexOfAge = CursorUtil.getColumnIndex(cursor, "ageColumn");
+    _entity = new User();
+    if (_cursorIndexOfUid != -1) {
+      _entity.uid = cursor.getInt(_cursorIndexOfUid);
+    }
+    if (_cursorIndexOfName != -1) {
+      if (cursor.isNull(_cursorIndexOfName)) {
+        _entity.name = null;
+      } else {
+        _entity.name = cursor.getString(_cursorIndexOfName);
+      }
+    }
+    if (_cursorIndexOfLastName != -1) {
+      final String _tmpLastName;
+      if (cursor.isNull(_cursorIndexOfLastName)) {
+        _tmpLastName = null;
+      } else {
+        _tmpLastName = cursor.getString(_cursorIndexOfLastName);
+      }
+      _entity.setLastName(_tmpLastName);
+    }
+    if (_cursorIndexOfAge != -1) {
+      _entity.age = cursor.getInt(_cursorIndexOfAge);
+    }
+    return _entity;
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
new file mode 100644
index 0000000..996eaf5
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
@@ -0,0 +1,367 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
+import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class DeletionDao_Impl implements DeletionDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
+
+  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+
+  private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
+
+  public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
+        if (entity.name == null) {
+          statement.bindNull(1);
+        } else {
+          statement.bindText(1, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.lastName);
+        }
+      }
+    };
+    this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `Book` WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+      }
+    };
+  }
+
+  @Override
+  public void deleteUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      return null;
+    });
+  }
+
+  @Override
+  public void deleteUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user1);
+      __deleteAdapterOfUser.handleMultiple(_connection, others);
+      return null;
+    });
+  }
+
+  @Override
+  public void deleteArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handleMultiple(_connection, users);
+      return null;
+    });
+  }
+
+  @Override
+  public Integer deleteUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user1);
+      _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
+      return _result;
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
+      return _result;
+    });
+  }
+
+  @Override
+  public Completable deleteUserCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> deleteUserSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> deleteUserMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
+      return _result;
+    });
+  }
+
+  @Override
+  public void deleteUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      __deleteAdapterOfBook.handle(_connection, book);
+      return null;
+    });
+  }
+
+  @Override
+  public int deleteByUid(final int uid) {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public Completable deleteByUidCompletable(final int uid) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> deleteByUidSingle(final int uid) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> deleteByUidMaybe(final int uid) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int deleteByUidList(final int... uid) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("DELETE FROM user where uid IN(");
+    final int _inputSize = uid == null ? 1 : uid.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (uid == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : uid) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
+        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public int deleteEverything() {
+    final String _sql = "DELETE FROM user";
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
new file mode 100644
index 0000000..6214715
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
@@ -0,0 +1,405 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class UpdateDao_Impl implements UpdateDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
+
+  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+
+  private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
+
+  public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindString(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindString(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
+        if (entity.name == null) {
+          statement.bindNull(1);
+        } else {
+          statement.bindText(1, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.lastName);
+        }
+        if (entity.name == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(4);
+        } else {
+          statement.bindText(4, entity.lastName);
+        }
+      }
+    };
+    this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    };
+  }
+
+  @Override
+  public void updateUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      return null;
+    });
+  }
+
+  @Override
+  public void updateUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user1);
+      __updateAdapterOfUser.handleMultiple(_connection, others);
+      return null;
+    });
+  }
+
+  @Override
+  public void updateArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handleMultiple(_connection, users);
+      return null;
+    });
+  }
+
+  @Override
+  public void updateTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser_1.handle(_connection, userOne);
+      __updateAdapterOfUser_1.handle(_connection, userTwo);
+      return null;
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user1);
+      _result += __updateAdapterOfUser.handleMultiple(_connection, others);
+      return _result;
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handleMultiple(_connection, users);
+      return _result;
+    });
+  }
+
+  @Override
+  public Integer updateUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
+
+  @Override
+  public Completable updateUserAndReturnCountCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> updateUserAndReturnCountSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
+      return _result;
+    });
+  }
+
+  @Override
+  public void updateUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      __updateAdapterOfBook.handle(_connection, book);
+      return null;
+    });
+  }
+
+  @Override
+  public void ageUserByUid(final String uid) {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (uid == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          _stmt.bindText(_argIndex, uid);
+        }
+        _stmt.step();
+        return null;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public void ageUserAll() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return null;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public Completable ageUserAllCompletable() {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> ageUserAllSingle() {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> ageUserAllMaybe() {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpsertDao.java
new file mode 100644
index 0000000..5cacce2
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpsertDao.java
@@ -0,0 +1,164 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.EntityUpsertAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteStatement;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class UpsertDao_Impl implements UpsertDao {
+  private final RoomDatabase __db;
+
+  private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
+
+  private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
+
+  public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    }, new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    });
+    this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    }, new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    });
+  }
+
+  @Override
+  public void upsertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user);
+      return null;
+    });
+  }
+
+  @Override
+  public void upsertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user1);
+      __upsertAdapterOfUser.upsert(_connection, others);
+      return null;
+    });
+  }
+
+  @Override
+  public void upsertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, users);
+      return null;
+    });
+  }
+
+  @Override
+  public void upsertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, userOne);
+      __upsertAdapterOfUser.upsert(_connection, userTwo);
+      return null;
+    });
+  }
+
+  @Override
+  public void upsertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user);
+      __upsertAdapterOfBook.upsert(_connection, book);
+      return null;
+    });
+  }
+
+  @Override
+  public long upsertAndReturnId(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
+    });
+  }
+
+  @Override
+  public long[] upsertAndReturnIdsArray(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/WriterDao.java
new file mode 100644
index 0000000..2f679db
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/WriterDao.java
@@ -0,0 +1,162 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteStatement;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class WriterDao_Impl implements WriterDao {
+  private final RoomDatabase __db;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
+
+  private final EntityInsertAdapter<Book> __insertAdapterOfBook;
+
+  public WriterDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR REPLACE INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    };
+  }
+
+  @Override
+  public void insertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user);
+      return null;
+    });
+  }
+
+  @Override
+  public void insertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user1);
+      __insertAdapterOfUser.insert(_connection, others);
+      return null;
+    });
+  }
+
+  @Override
+  public void insertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser_1.insert(_connection, users);
+      return null;
+    });
+  }
+
+  @Override
+  public void insertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser_2.insert(_connection, userOne);
+      __insertAdapterOfUser_2.insert(_connection, userTwo);
+      return null;
+    });
+  }
+
+  @Override
+  public void insertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user);
+      __insertAdapterOfBook.insert(_connection, book);
+      return null;
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
new file mode 100644
index 0000000..c750934
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
@@ -0,0 +1,764 @@
+package foo.bar;
+
+import android.database.Cursor;
+import android.os.CancellationSignal;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomSQLiteQuery;
+import androidx.room.guava.GuavaRoom;
+import androidx.room.util.CursorUtil;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteStatementUtil;
+import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.lang.Boolean;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.lang.SuppressWarnings;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class ComplexDao_Impl extends ComplexDao {
+  private final RoomDatabase __db;
+
+  public ComplexDao_Impl(final ComplexDatabase __db) {
+    super(__db);
+    this.__db = __db;
+  }
+
+  @Override
+  public boolean transactionMethod(final int i, final String s, final long l) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Boolean>() {
+      @Override
+      @NonNull
+      public Boolean invoke(@NonNull final SQLiteConnection _connection) {
+        return ComplexDao_Impl.super.transactionMethod(i, s, l);
+      }
+    });
+  }
+
+  @Override
+  public List<ComplexDao.FullName> fullNames(final int id) {
+    final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<ComplexDao.FullName>>() {
+      @Override
+      @NonNull
+      public List<ComplexDao.FullName> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, id);
+          final int _cursorIndexOfFullName = 0;
+          final int _cursorIndexOfId = 1;
+          final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
+          while (_stmt.step()) {
+            final ComplexDao.FullName _item;
+            _item = new ComplexDao.FullName();
+            if (_stmt.isNull(_cursorIndexOfFullName)) {
+              _item.fullName = null;
+            } else {
+              _item.fullName = _stmt.getText(_cursorIndexOfFullName);
+            }
+            _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public User getById(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
+      @Override
+      @NonNull
+      public User invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, id);
+          final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+          final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+          final User _result;
+          if (_stmt.step()) {
+            _result = new User();
+            _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _result.name = null;
+            } else {
+              _result.name = _stmt.getText(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_stmt.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+            }
+            _result.setLastName(_tmpLastName);
+            _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          } else {
+            _result = null;
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public User findByName(final String name, final String lastName) {
+    final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
+      @Override
+      @NonNull
+      public User invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (name == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            _stmt.bindText(_argIndex, name);
+          }
+          _argIndex = 2;
+          if (lastName == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            _stmt.bindText(_argIndex, lastName);
+          }
+          final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+          final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+          final User _result;
+          if (_stmt.step()) {
+            _result = new User();
+            _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _result.name = null;
+            } else {
+              _result.name = _stmt.getText(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_stmt.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+            }
+            _result.setLastName(_tmpLastName);
+            _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          } else {
+            _result = null;
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public List<User> loadAllByIds(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<User>>() {
+      @Override
+      @NonNull
+      public List<User> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (ids == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item : ids) {
+              _stmt.bindLong(_argIndex, _item);
+              _argIndex++;
+            }
+          }
+          final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+          final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+          final List<User> _result = new ArrayList<User>();
+          while (_stmt.step()) {
+            final User _item_1;
+            _item_1 = new User();
+            _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _item_1.name = null;
+            } else {
+              _item_1.name = _stmt.getText(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_stmt.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+            }
+            _item_1.setLastName(_tmpLastName);
+            _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+            _result.add(_item_1);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  int getAge(final int id) {
+    final String _sql = "SELECT ageColumn FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, id);
+          final int _result;
+          if (_stmt.step()) {
+            _result = (int) (_stmt.getLong(0));
+          } else {
+            _result = 0;
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int[] getAllAges(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, int[]>() {
+      @Override
+      @NonNull
+      public int[] invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (ids == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item : ids) {
+              _stmt.bindLong(_argIndex, _item);
+              _argIndex++;
+            }
+          }
+          final List<Integer> _listResult = new ArrayList<Integer>();
+          while (_stmt.step()) {
+            final Integer _item_1;
+            if (_stmt.isNull(0)) {
+              _item_1 = null;
+            } else {
+              _item_1 = (int) (_stmt.getLong(0));
+            }
+            _listResult.add(_item_1);
+          }
+          final int[] _tmpArrayResult = new int[_listResult.size()];
+          int _index = 0;
+          for (int _listItem : _listResult) {
+            _tmpArrayResult[_index] = _listItem;
+            _index++;
+          }
+          final int[] _result = _tmpArrayResult;
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+      @Override
+      @NonNull
+      public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (ids == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (Integer _item : ids) {
+              if (_item == null) {
+                _stmt.bindNull(_argIndex);
+              } else {
+                _stmt.bindLong(_argIndex, _item);
+              }
+              _argIndex++;
+            }
+          }
+          final List<Integer> _result = new ArrayList<Integer>();
+          while (_stmt.step()) {
+            final Integer _item_1;
+            if (_stmt.isNull(0)) {
+              _item_1 = null;
+            } else {
+              _item_1 = (int) (_stmt.getLong(0));
+            }
+            _result.add(_item_1);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
+      final int... ids3) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids1 == null ? 1 : ids1.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_1 = ids2 == null ? 1 : ids2.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_2 = ids3 == null ? 1 : ids3.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+      @Override
+      @NonNull
+      public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (ids1 == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (Integer _item : ids1) {
+              if (_item == null) {
+                _stmt.bindNull(_argIndex);
+              } else {
+                _stmt.bindLong(_argIndex, _item);
+              }
+              _argIndex++;
+            }
+          }
+          _argIndex = 1 + _inputSize;
+          if (ids2 == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item_1 : ids2) {
+              _stmt.bindLong(_argIndex, _item_1);
+              _argIndex++;
+            }
+          }
+          _argIndex = 1 + _inputSize + _inputSize_1;
+          if (ids3 == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item_2 : ids3) {
+              _stmt.bindLong(_argIndex, _item_2);
+              _argIndex++;
+            }
+          }
+          final List<Integer> _result = new ArrayList<Integer>();
+          while (_stmt.step()) {
+            final Integer _item_3;
+            if (_stmt.isNull(0)) {
+              _item_3 = null;
+            } else {
+              _item_3 = (int) (_stmt.getLong(0));
+            }
+            _result.add(_item_3);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public LiveData<User> getByIdLive(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+    int _argIndex = 1;
+    _statement.bindLong(_argIndex, id);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
+      @Override
+      @Nullable
+      public User call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final User _result;
+          if (_cursor.moveToFirst()) {
+            _result = new User();
+            _result.uid = _cursor.getInt(_cursorIndexOfUid);
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _result.name = null;
+            } else {
+              _result.name = _cursor.getString(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_cursor.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            }
+            _result.setLastName(_tmpLastName);
+            _result.age = _cursor.getInt(_cursorIndexOfAge);
+          } else {
+            _result = null;
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    final int _argCount = 0 + _inputSize;
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+    int _argIndex = 1;
+    if (ids == null) {
+      _statement.bindNull(_argIndex);
+    } else {
+      for (int _item : ids) {
+        _statement.bindLong(_argIndex, _item);
+        _argIndex++;
+      }
+    }
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
+      @Override
+      @Nullable
+      public List<User> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final List<User> _result = new ArrayList<User>();
+          while (_cursor.moveToNext()) {
+            final User _item_1;
+            _item_1 = new User();
+            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _item_1.name = null;
+            } else {
+              _item_1.name = _cursor.getString(_cursorIndexOfName);
+            }
+            final String _tmpLastName;
+            if (_cursor.isNull(_cursorIndexOfLastName)) {
+              _tmpLastName = null;
+            } else {
+              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            }
+            _item_1.setLastName(_tmpLastName);
+            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+            _result.add(_item_1);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public List<Child1> getChild1List() {
+    final String _sql = "SELECT * FROM Child1";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
+      @Override
+      @NonNull
+      public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+          final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+          final List<Child1> _result = new ArrayList<Child1>();
+          while (_stmt.step()) {
+            final Child1 _item;
+            final int _tmpId;
+            _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+            final String _tmpName;
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _tmpName = null;
+            } else {
+              _tmpName = _stmt.getText(_cursorIndexOfName);
+            }
+            final Info _tmpInfo;
+            if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+              _tmpInfo = new Info();
+              _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+              if (_stmt.isNull(_cursorIndexOfCode)) {
+                _tmpInfo.code = null;
+              } else {
+                _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+              }
+            } else {
+              _tmpInfo = null;
+            }
+            _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public List<Child2> getChild2List() {
+    final String _sql = "SELECT * FROM Child2";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child2>>() {
+      @Override
+      @NonNull
+      public List<Child2> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+          final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+          final List<Child2> _result = new ArrayList<Child2>();
+          while (_stmt.step()) {
+            final Child2 _item;
+            final int _tmpId;
+            _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+            final String _tmpName;
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _tmpName = null;
+            } else {
+              _tmpName = _stmt.getText(_cursorIndexOfName);
+            }
+            final Info _tmpInfo;
+            if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+              _tmpInfo = new Info();
+              _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+              if (_stmt.isNull(_cursorIndexOfCode)) {
+                _tmpInfo.code = null;
+              } else {
+                _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+              }
+            } else {
+              _tmpInfo = null;
+            }
+            _item = new Child2(_tmpId,_tmpName,_tmpInfo);
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public ListenableFuture<List<Child1>> getChild1ListListenableFuture() {
+    final String _sql = "SELECT * FROM Child1";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
+    final CancellationSignal _cancellationSignal = new CancellationSignal();
+    return GuavaRoom.createListenableFuture(__db, false, new Callable<List<Child1>>() {
+      @Override
+      public List<Child1> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, _cancellationSignal);
+        try {
+          final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
+          final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
+          final List<Child1> _result = new ArrayList<Child1>();
+          while (_cursor.moveToNext()) {
+            final Child1 _item;
+            final int _tmpId;
+            _tmpId = _cursor.getInt(_cursorIndexOfId);
+            final String _tmpName;
+            if (_cursor.isNull(_cursorIndexOfName)) {
+              _tmpName = null;
+            } else {
+              _tmpName = _cursor.getString(_cursorIndexOfName);
+            }
+            final Info _tmpInfo;
+            if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
+              _tmpInfo = new Info();
+              _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
+              if (_cursor.isNull(_cursorIndexOfCode)) {
+                _tmpInfo.code = null;
+              } else {
+                _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
+              }
+            } else {
+              _tmpInfo = null;
+            }
+            _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+    }, _statement, true, _cancellationSignal);
+  }
+
+  @Override
+  public List<UserSummary> getUserNames() {
+    final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
+    return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<UserSummary>>() {
+      @Override
+      @NonNull
+      public List<UserSummary> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          final int _cursorIndexOfUid = 0;
+          final int _cursorIndexOfName = 1;
+          final List<UserSummary> _result = new ArrayList<UserSummary>();
+          while (_stmt.step()) {
+            final UserSummary _item;
+            _item = new UserSummary();
+            _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
+              _item.name = null;
+            } else {
+              _item.name = _stmt.getText(_cursorIndexOfName);
+            }
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public User getUserViaRawQuery(final SupportSQLiteQuery rawQuery) {
+    __db.assertNotSuspendingTransaction();
+    final Cursor _cursor = DBUtil.query(__db, rawQuery, false, null);
+    try {
+      final User _result;
+      if (_cursor.moveToFirst()) {
+        _result = __entityCursorConverter_fooBarUser(_cursor);
+      } else {
+        _result = null;
+      }
+      return _result;
+    } finally {
+      _cursor.close();
+    }
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+
+  private User __entityCursorConverter_fooBarUser(@NonNull final Cursor cursor) {
+    final User _entity;
+    final int _cursorIndexOfUid = CursorUtil.getColumnIndex(cursor, "uid");
+    final int _cursorIndexOfName = CursorUtil.getColumnIndex(cursor, "name");
+    final int _cursorIndexOfLastName = CursorUtil.getColumnIndex(cursor, "lastName");
+    final int _cursorIndexOfAge = CursorUtil.getColumnIndex(cursor, "ageColumn");
+    _entity = new User();
+    if (_cursorIndexOfUid != -1) {
+      _entity.uid = cursor.getInt(_cursorIndexOfUid);
+    }
+    if (_cursorIndexOfName != -1) {
+      if (cursor.isNull(_cursorIndexOfName)) {
+        _entity.name = null;
+      } else {
+        _entity.name = cursor.getString(_cursorIndexOfName);
+      }
+    }
+    if (_cursorIndexOfLastName != -1) {
+      final String _tmpLastName;
+      if (cursor.isNull(_cursorIndexOfLastName)) {
+        _tmpLastName = null;
+      } else {
+        _tmpLastName = cursor.getString(_cursorIndexOfLastName);
+      }
+      _entity.setLastName(_tmpLastName);
+    }
+    if (_cursorIndexOfAge != -1) {
+      _entity.age = cursor.getInt(_cursorIndexOfAge);
+    }
+    return _entity;
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
new file mode 100644
index 0000000..e287d17
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
@@ -0,0 +1,417 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
+import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class DeletionDao_Impl implements DeletionDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
+
+  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+
+  private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
+
+  public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
+        if (entity.name == null) {
+          statement.bindNull(1);
+        } else {
+          statement.bindText(1, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.lastName);
+        }
+      }
+    };
+    this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `Book` WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+      }
+    };
+  }
+
+  @Override
+  public void deleteUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __deleteAdapterOfUser.handle(_connection, user);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void deleteUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __deleteAdapterOfUser.handle(_connection, user1);
+        __deleteAdapterOfUser.handleMultiple(_connection, others);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void deleteArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __deleteAdapterOfUser.handleMultiple(_connection, users);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public Integer deleteUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handle(_connection, user);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handle(_connection, user);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handle(_connection, user1);
+        _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public Completable deleteUserCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> deleteUserSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> deleteUserMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public void deleteUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __deleteAdapterOfUser.handle(_connection, user);
+        __deleteAdapterOfBook.handle(_connection, book);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public int deleteByUid(final int uid) {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, uid);
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Completable deleteByUidCompletable(final int uid) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> deleteByUidSingle(final int uid) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> deleteByUidMaybe(final int uid) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int deleteByUidList(final int... uid) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("DELETE FROM user where uid IN(");
+    final int _inputSize = uid == null ? 1 : uid.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (uid == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item : uid) {
+              _stmt.bindLong(_argIndex, _item);
+              _argIndex++;
+            }
+          }
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int deleteEverything() {
+    final String _sql = "DELETE FROM user";
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
new file mode 100644
index 0000000..f3ca3df
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
@@ -0,0 +1,455 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class UpdateDao_Impl implements UpdateDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
+
+  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
+
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+
+  private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
+
+  public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindString(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindString(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
+        if (entity.name == null) {
+          statement.bindNull(1);
+        } else {
+          statement.bindText(1, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.lastName);
+        }
+        if (entity.name == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.name);
+        }
+        if (entity.lastName == null) {
+          statement.bindNull(4);
+        } else {
+          statement.bindText(4, entity.lastName);
+        }
+      }
+    };
+    this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    };
+  }
+
+  @Override
+  public void updateUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser.handle(_connection, user);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void updateUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser.handle(_connection, user1);
+        __updateAdapterOfUser.handleMultiple(_connection, others);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void updateArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser.handleMultiple(_connection, users);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void updateTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser_1.handle(_connection, userOne);
+        __updateAdapterOfUser_1.handle(_connection, userTwo);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handle(_connection, user);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handle(_connection, user1);
+        _result += __updateAdapterOfUser.handleMultiple(_connection, others);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public int updateUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handleMultiple(_connection, users);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public Integer updateUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handle(_connection, user);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public Completable updateUserAndReturnCountCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> updateUserAndReturnCountSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+      @Override
+      @NonNull
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
+        return _result;
+      }
+    });
+  }
+
+  @Override
+  public void updateUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser.handle(_connection, user);
+        __updateAdapterOfBook.handle(_connection, book);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void ageUserByUid(final String uid) {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          int _argIndex = 1;
+          if (uid == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            _stmt.bindText(_argIndex, uid);
+          }
+          _stmt.step();
+          return null;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public void ageUserAll() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
+        try {
+          _stmt.step();
+          return null;
+        } finally {
+          _stmt.close();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Completable ageUserAllCompletable() {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Single<Integer> ageUserAllSingle() {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @Override
+  public Maybe<Integer> ageUserAllMaybe() {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpsertDao.java
new file mode 100644
index 0000000..f6dbf33
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpsertDao.java
@@ -0,0 +1,196 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.EntityUpsertAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
+import java.lang.Class;
+import java.lang.Long;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class UpsertDao_Impl implements UpsertDao {
+  private final RoomDatabase __db;
+
+  private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
+
+  private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
+
+  public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    }, new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    });
+    this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    }, new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    });
+  }
+
+  @Override
+  public void upsertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __upsertAdapterOfUser.upsert(_connection, user);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void upsertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __upsertAdapterOfUser.upsert(_connection, user1);
+        __upsertAdapterOfUser.upsert(_connection, others);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void upsertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __upsertAdapterOfUser.upsert(_connection, users);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void upsertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __upsertAdapterOfUser.upsert(_connection, userOne);
+        __upsertAdapterOfUser.upsert(_connection, userTwo);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void upsertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __upsertAdapterOfUser.upsert(_connection, user);
+        __upsertAdapterOfBook.upsert(_connection, book);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public long upsertAndReturnId(final User user) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Long>() {
+      @Override
+      @NonNull
+      public Long invoke(@NonNull final SQLiteConnection _connection) {
+        return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
+      }
+    });
+  }
+
+  @Override
+  public long[] upsertAndReturnIdsArray(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, long[]>() {
+      @Override
+      @NonNull
+      public long[] invoke(@NonNull final SQLiteConnection _connection) {
+        return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/WriterDao.java
new file mode 100644
index 0000000..6402763
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/WriterDao.java
@@ -0,0 +1,185 @@
+package foo.bar;
+
+import androidx.annotation.NonNull;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.SuppressWarnings;
+import java.lang.Void;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
+
+@Generated("androidx.room.RoomProcessor")
+@SuppressWarnings({"unchecked", "deprecation", "removal"})
+public final class WriterDao_Impl implements WriterDao {
+  private final RoomDatabase __db;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
+
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
+
+  private final EntityInsertAdapter<Book> __insertAdapterOfBook;
+
+  public WriterDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR REPLACE INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+        statement.bindLong(1, entity.uid);
+        if (entity.name == null) {
+          statement.bindNull(2);
+        } else {
+          statement.bindText(2, entity.name);
+        }
+        if (entity.getLastName() == null) {
+          statement.bindNull(3);
+        } else {
+          statement.bindText(3, entity.getLastName());
+        }
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
+
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    };
+  }
+
+  @Override
+  public void insertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __insertAdapterOfUser.insert(_connection, user);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void insertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __insertAdapterOfUser.insert(_connection, user1);
+        __insertAdapterOfUser.insert(_connection, others);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void insertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __insertAdapterOfUser_1.insert(_connection, users);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void insertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __insertAdapterOfUser_2.insert(_connection, userOne);
+        __insertAdapterOfUser_2.insert(_connection, userTwo);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void insertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+      @Override
+      @NonNull
+      public Void invoke(@NonNull final SQLiteConnection _connection) {
+        __insertAdapterOfUser.insert(_connection, user);
+        __insertAdapterOfBook.insert(_connection, book);
+        return null;
+      }
+    });
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index 275e55d..a9c2362 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -12,7 +12,6 @@
 import androidx.room.util.DBUtil;
 import androidx.room.util.SQLiteStatementUtil;
 import androidx.room.util.StringUtil;
-import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
 import androidx.sqlite.db.SupportSQLiteQuery;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -28,640 +27,595 @@
 import java.util.List;
 import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
 public final class ComplexDao_Impl extends ComplexDao {
-    private final RoomDatabase __db;
+  private final RoomDatabase __db;
 
-    public ComplexDao_Impl(final ComplexDatabase __db) {
-        super(__db);
-        this.__db = __db;
-    }
+  public ComplexDao_Impl(final ComplexDatabase __db) {
+    super(__db);
+    this.__db = __db;
+  }
 
-    @Override
-    public boolean transactionMethod(final int i, final String s, final long l) {
-        return DBUtil.performBlocking(__db, false, true, (_connection) -> {
-            return ComplexDao_Impl.super.transactionMethod(i, s, l);
-        });
-    }
+  @Override
+  public boolean transactionMethod(final int i, final String s, final long l) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return ComplexDao_Impl.super.transactionMethod(i, s, l);
+    });
+  }
 
-    @Override
-    public List<ComplexDao.FullName> fullNames(final int id) {
-        final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<ComplexDao.FullName>>() {
-            @Override
-            @NonNull
-            public List<ComplexDao.FullName> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _cursorIndexOfFullName = 0;
-                    final int _cursorIndexOfId = 1;
-                    final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
-                    while (_stmt.step()) {
-                        final ComplexDao.FullName _item;
-                        _item = new ComplexDao.FullName();
-                        _item.fullName = _stmt.getText(_cursorIndexOfFullName);
-                        _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User getById(final int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
-            @Override
-            @NonNull
-            public User invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final User _result;
-                    if (_stmt.step()) {
-                        _result = new User();
-                        _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        _result.name = _stmt.getText(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        _result.setLastName(_tmpLastName);
-                        _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User findByName(final String name, final String lastName) {
-        final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
-            @Override
-            @NonNull
-            public User invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindText(_argIndex, name);
-                    _argIndex = 2;
-                    _stmt.bindText(_argIndex, lastName);
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final User _result;
-                    if (_stmt.step()) {
-                        _result = new User();
-                        _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        _result.name = _stmt.getText(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        _result.setLastName(_tmpLastName);
-                        _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<User> loadAllByIds(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<User>>() {
-            @Override
-            @NonNull
-            public List<User> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : ids) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
-                    final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
-                    final List<User> _result = new ArrayList<User>();
-                    while (_stmt.step()) {
-                        final User _item_1;
-                        _item_1 = new User();
-                        _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        _item_1.name = _stmt.getText(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
-                        _item_1.setLastName(_tmpLastName);
-                        _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    int getAge(final int id) {
-        final String _sql = "SELECT ageColumn FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id);
-                    final int _result;
-                    if (_stmt.step()) {
-                        _result = (int) (_stmt.getLong(0));
-                    } else {
-                        _result = 0;
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public int[] getAllAges(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, int[]>() {
-            @Override
-            @NonNull
-            public int[] invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : ids) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _listResult = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_1;
-                        _item_1 = (int) (_stmt.getLong(0));
-                        _listResult.add(_item_1);
-                    }
-                    final int[] _tmpArrayResult = new int[_listResult.size()];
-                    int _index = 0;
-                    for (int _listItem : _listResult) {
-                        _tmpArrayResult[_index] = _listItem;
-                        _index++;
-                    }
-                    final int[] _result = _tmpArrayResult;
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<Integer> getAllAgesAsList(final List<Integer> ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids == null ? 1 : ids.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
-            @Override
-            @NonNull
-            public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (Integer _item : ids) {
-                            if (_item == null) {
-                                _stmt.bindNull(_argIndex);
-                            } else {
-                                _stmt.bindLong(_argIndex, _item);
-                            }
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _result = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_1;
-                        if (_stmt.isNull(0)) {
-                            _item_1 = null;
-                        } else {
-                            _item_1 = (int) (_stmt.getLong(0));
-                        }
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
-            final int... ids3) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids1 == null ? 1 : ids1.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_1 = ids2 == null ? 1 : ids2.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_2 = ids3 == null ? 1 : ids3.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
-            @Override
-            @NonNull
-            public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (ids1 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (Integer _item : ids1) {
-                            if (_item == null) {
-                                _stmt.bindNull(_argIndex);
-                            } else {
-                                _stmt.bindLong(_argIndex, _item);
-                            }
-                            _argIndex++;
-                        }
-                    }
-                    _argIndex = 1 + _inputSize;
-                    if (ids2 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item_1 : ids2) {
-                            _stmt.bindLong(_argIndex, _item_1);
-                            _argIndex++;
-                        }
-                    }
-                    _argIndex = 1 + _inputSize + _inputSize_1;
-                    if (ids3 == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item_2 : ids3) {
-                            _stmt.bindLong(_argIndex, _item_2);
-                            _argIndex++;
-                        }
-                    }
-                    final List<Integer> _result = new ArrayList<Integer>();
-                    while (_stmt.step()) {
-                        final Integer _item_3;
-                        if (_stmt.isNull(0)) {
-                            _item_3 = null;
-                        } else {
-                            _item_3 = (int) (_stmt.getLong(0));
-                        }
-                        _result.add(_item_3);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public LiveData<User> getByIdLive(final int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+  @Override
+  public List<ComplexDao.FullName> fullNames(final int id) {
+    final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
-            @Override
-            @Nullable
-            public User call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-                try {
-                    final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-                    final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-                    final User _result;
-                    if (_cursor.moveToFirst()) {
-                        _result = new User();
-                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
-                        _result.name = _cursor.getString(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        _result.setLastName(_tmpLastName);
-                        _result.age = _cursor.getInt(_cursorIndexOfAge);
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfFullName = 0;
+        final int _cursorIndexOfId = 1;
+        final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
+        while (_stmt.step()) {
+          final ComplexDao.FullName _item;
+          _item = new ComplexDao.FullName();
+          _item.fullName = _stmt.getText(_cursorIndexOfFullName);
+          _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        });
-    }
+  @Override
+  public User getById(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          _result.name = _stmt.getText(_cursorIndexOfName);
+          final String _tmpLastName;
+          _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids == null ? 1 : ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+  @Override
+  public User findByName(final String name, final String lastName) {
+    final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindText(_argIndex, name);
+        _argIndex = 2;
+        _stmt.bindText(_argIndex, lastName);
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          _result.name = _stmt.getText(_cursorIndexOfName);
+          final String _tmpLastName;
+          _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<User> loadAllByIds(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         if (ids == null) {
-            _statement.bindNull(_argIndex);
+          _stmt.bindNull(_argIndex);
         } else {
-            for (int _item : ids) {
-                _statement.bindLong(_argIndex, _item);
-                _argIndex++;
-            }
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
         }
-        return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
-            @Override
-            @Nullable
-            public List<User> call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-                try {
-                    final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-                    final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-                    final List<User> _result = new ArrayList<User>();
-                    while (_cursor.moveToNext()) {
-                        final User _item_1;
-                        _item_1 = new User();
-                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-                        _item_1.name = _cursor.getString(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        _item_1.setLastName(_tmpLastName);
-                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final List<User> _result = new ArrayList<User>();
+        while (_stmt.step()) {
+          final User _item_1;
+          _item_1 = new User();
+          _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          _item_1.name = _stmt.getText(_cursorIndexOfName);
+          final String _tmpLastName;
+          _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          _item_1.setLastName(_tmpLastName);
+          _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        });
-    }
+  @Override
+  int getAge(final int id) {
+    final String _sql = "SELECT ageColumn FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _result;
+        if (_stmt.step()) {
+          _result = (int) (_stmt.getLong(0));
+        } else {
+          _result = 0;
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public List<Child1> getChild1List() {
-        final String _sql = "SELECT * FROM Child1";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
-            @Override
-            @NonNull
-            public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
-                    final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
-                    final List<Child1> _result = new ArrayList<Child1>();
-                    while (_stmt.step()) {
-                        final Child1 _item;
-                        final int _tmpId;
-                        _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
-                        final String _tmpName;
-                        _tmpName = _stmt.getText(_cursorIndexOfName);
-                        final Info _tmpInfo;
-                        if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
-                            _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
+  @Override
+  public int[] getAllAges(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
+        }
+        final List<Integer> _listResult = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_1;
+          _item_1 = (int) (_stmt.getLong(0));
+          _listResult.add(_item_1);
+        }
+        final int[] _tmpArrayResult = new int[_listResult.size()];
+        int _index = 0;
+        for (int _listItem : _listResult) {
+          _tmpArrayResult[_index] = _listItem;
+          _index++;
+        }
+        final int[] _result = _tmpArrayResult;
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public List<Child2> getChild2List() {
-        final String _sql = "SELECT * FROM Child2";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child2>>() {
-            @Override
-            @NonNull
-            public List<Child2> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
-                    final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
-                    final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
-                    final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
-                    final List<Child2> _result = new ArrayList<Child2>();
-                    while (_stmt.step()) {
-                        final Child2 _item;
-                        final int _tmpId;
-                        _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
-                        final String _tmpName;
-                        _tmpName = _stmt.getText(_cursorIndexOfName);
-                        final Info _tmpInfo;
-                        if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
-                            _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child2(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<List<Child1>> getChild1ListListenableFuture() {
-        final String _sql = "SELECT * FROM Child1";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
-        final CancellationSignal _cancellationSignal = new CancellationSignal();
-        return GuavaRoom.createListenableFuture(__db, false, new Callable<List<Child1>>() {
-            @Override
-            public List<Child1> call() throws Exception {
-                final Cursor _cursor = DBUtil.query(__db, _statement, false, _cancellationSignal);
-                try {
-                    final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
-                    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-                    final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
-                    final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
-                    final List<Child1> _result = new ArrayList<Child1>();
-                    while (_cursor.moveToNext()) {
-                        final Child1 _item;
-                        final int _tmpId;
-                        _tmpId = _cursor.getInt(_cursorIndexOfId);
-                        final String _tmpName;
-                        _tmpName = _cursor.getString(_cursorIndexOfName);
-                        final Info _tmpInfo;
-                        if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
-                            _tmpInfo = new Info();
-                            _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
-                            _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
-                        } else {
-                            _tmpInfo = null;
-                        }
-                        _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
-        }, _statement, true, _cancellationSignal);
-    }
-
-    @Override
-    public List<UserSummary> getUserNames() {
-        final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
-        return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<UserSummary>>() {
-            @Override
-            @NonNull
-            public List<UserSummary> invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    final int _cursorIndexOfUid = 0;
-                    final int _cursorIndexOfName = 1;
-                    final List<UserSummary> _result = new ArrayList<UserSummary>();
-                    while (_stmt.step()) {
-                        final UserSummary _item;
-                        _item = new UserSummary();
-                        _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
-                        _item.name = _stmt.getText(_cursorIndexOfName);
-                        _result.add(_item);
-                    }
-                    return _result;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @Override
-    public User getUserViaRawQuery(final SupportSQLiteQuery rawQuery) {
-        __db.assertNotSuspendingTransaction();
-        final Cursor _cursor = DBUtil.query(__db, rawQuery, false, null);
-        try {
-            final User _result;
-            if (_cursor.moveToFirst()) {
-                _result = __entityCursorConverter_fooBarUser(_cursor);
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids == null ? 1 : ids.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (Integer _item : ids) {
+            if (_item == null) {
+              _stmt.bindNull(_argIndex);
             } else {
-                _result = null;
+              _stmt.bindLong(_argIndex, _item);
             }
-            return _result;
-        } finally {
-            _cursor.close();
+            _argIndex++;
+          }
         }
-    }
+        final List<Integer> _result = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_1;
+          if (_stmt.isNull(0)) {
+            _item_1 = null;
+          } else {
+            _item_1 = (int) (_stmt.getLong(0));
+          }
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
+  @Override
+  public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
+      final int... ids3) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+    final int _inputSize = ids1 == null ? 1 : ids1.size();
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_1 = ids2 == null ? 1 : ids2.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+    _stringBuilder.append(") OR uid IN (");
+    final int _inputSize_2 = ids3 == null ? 1 : ids3.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids1 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (Integer _item : ids1) {
+            if (_item == null) {
+              _stmt.bindNull(_argIndex);
+            } else {
+              _stmt.bindLong(_argIndex, _item);
+            }
+            _argIndex++;
+          }
+        }
+        _argIndex = 1 + _inputSize;
+        if (ids2 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item_1 : ids2) {
+            _stmt.bindLong(_argIndex, _item_1);
+            _argIndex++;
+          }
+        }
+        _argIndex = 1 + _inputSize + _inputSize_1;
+        if (ids3 == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item_2 : ids3) {
+            _stmt.bindLong(_argIndex, _item_2);
+            _argIndex++;
+          }
+        }
+        final List<Integer> _result = new ArrayList<Integer>();
+        while (_stmt.step()) {
+          final Integer _item_3;
+          if (_stmt.isNull(0)) {
+            _item_3 = null;
+          } else {
+            _item_3 = (int) (_stmt.getLong(0));
+          }
+          _result.add(_item_3);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    private User __entityCursorConverter_fooBarUser(@NonNull final Cursor cursor) {
-        final User _entity;
-        final int _cursorIndexOfUid = CursorUtil.getColumnIndex(cursor, "uid");
-        final int _cursorIndexOfName = CursorUtil.getColumnIndex(cursor, "name");
-        final int _cursorIndexOfLastName = CursorUtil.getColumnIndex(cursor, "lastName");
-        final int _cursorIndexOfAge = CursorUtil.getColumnIndex(cursor, "ageColumn");
-        _entity = new User();
-        if (_cursorIndexOfUid != -1) {
-            _entity.uid = cursor.getInt(_cursorIndexOfUid);
-        }
-        if (_cursorIndexOfName != -1) {
-            _entity.name = cursor.getString(_cursorIndexOfName);
-        }
-        if (_cursorIndexOfLastName != -1) {
+  @Override
+  public LiveData<User> getByIdLive(final int id) {
+    final String _sql = "SELECT * FROM user where uid = ?";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+    int _argIndex = 1;
+    _statement.bindLong(_argIndex, id);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
+      @Override
+      @Nullable
+      public User call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final User _result;
+          if (_cursor.moveToFirst()) {
+            _result = new User();
+            _result.uid = _cursor.getInt(_cursorIndexOfUid);
+            _result.name = _cursor.getString(_cursorIndexOfName);
             final String _tmpLastName;
-            _tmpLastName = cursor.getString(_cursorIndexOfLastName);
-            _entity.setLastName(_tmpLastName);
+            _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            _result.setLastName(_tmpLastName);
+            _result.age = _cursor.getInt(_cursorIndexOfAge);
+          } else {
+            _result = null;
+          }
+          return _result;
+        } finally {
+          _cursor.close();
         }
-        if (_cursorIndexOfAge != -1) {
-            _entity.age = cursor.getInt(_cursorIndexOfAge);
-        }
-        return _entity;
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("SELECT * FROM user where uid IN (");
+    final int _inputSize = ids == null ? 1 : ids.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    final int _argCount = 0 + _inputSize;
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+    int _argIndex = 1;
+    if (ids == null) {
+      _statement.bindNull(_argIndex);
+    } else {
+      for (int _item : ids) {
+        _statement.bindLong(_argIndex, _item);
+        _argIndex++;
+      }
     }
-}
\ No newline at end of file
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
+      @Override
+      @Nullable
+      public List<User> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+        try {
+          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
+          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          final List<User> _result = new ArrayList<User>();
+          while (_cursor.moveToNext()) {
+            final User _item_1;
+            _item_1 = new User();
+            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+            _item_1.name = _cursor.getString(_cursorIndexOfName);
+            final String _tmpLastName;
+            _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+            _item_1.setLastName(_tmpLastName);
+            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+            _result.add(_item_1);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+
+      @Override
+      protected void finalize() {
+        _statement.release();
+      }
+    });
+  }
+
+  @Override
+  public List<Child1> getChild1List() {
+    final String _sql = "SELECT * FROM Child1";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+        final List<Child1> _result = new ArrayList<Child1>();
+        while (_stmt.step()) {
+          final Child1 _item;
+          final int _tmpId;
+          _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+          final String _tmpName;
+          _tmpName = _stmt.getText(_cursorIndexOfName);
+          final Info _tmpInfo;
+          if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+            _tmpInfo = new Info();
+            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+            _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+          } else {
+            _tmpInfo = null;
+          }
+          _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public List<Child2> getChild2List() {
+    final String _sql = "SELECT * FROM Child2";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+        final List<Child2> _result = new ArrayList<Child2>();
+        while (_stmt.step()) {
+          final Child2 _item;
+          final int _tmpId;
+          _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+          final String _tmpName;
+          _tmpName = _stmt.getText(_cursorIndexOfName);
+          final Info _tmpInfo;
+          if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+            _tmpInfo = new Info();
+            _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+            _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+          } else {
+            _tmpInfo = null;
+          }
+          _item = new Child2(_tmpId,_tmpName,_tmpInfo);
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public ListenableFuture<List<Child1>> getChild1ListListenableFuture() {
+    final String _sql = "SELECT * FROM Child1";
+    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
+    final CancellationSignal _cancellationSignal = new CancellationSignal();
+    return GuavaRoom.createListenableFuture(__db, false, new Callable<List<Child1>>() {
+      @Override
+      public List<Child1> call() throws Exception {
+        final Cursor _cursor = DBUtil.query(__db, _statement, false, _cancellationSignal);
+        try {
+          final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
+          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
+          final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
+          final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
+          final List<Child1> _result = new ArrayList<Child1>();
+          while (_cursor.moveToNext()) {
+            final Child1 _item;
+            final int _tmpId;
+            _tmpId = _cursor.getInt(_cursorIndexOfId);
+            final String _tmpName;
+            _tmpName = _cursor.getString(_cursorIndexOfName);
+            final Info _tmpInfo;
+            if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
+              _tmpInfo = new Info();
+              _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
+              _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
+            } else {
+              _tmpInfo = null;
+            }
+            _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+            _result.add(_item);
+          }
+          return _result;
+        } finally {
+          _cursor.close();
+        }
+      }
+    }, _statement, true, _cancellationSignal);
+  }
+
+  @Override
+  public List<UserSummary> getUserNames() {
+    final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
+    return DBUtil.performBlocking(__db, true, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        final int _cursorIndexOfUid = 0;
+        final int _cursorIndexOfName = 1;
+        final List<UserSummary> _result = new ArrayList<UserSummary>();
+        while (_stmt.step()) {
+          final UserSummary _item;
+          _item = new UserSummary();
+          _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          _item.name = _stmt.getText(_cursorIndexOfName);
+          _result.add(_item);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public User getUserViaRawQuery(final SupportSQLiteQuery rawQuery) {
+    __db.assertNotSuspendingTransaction();
+    final Cursor _cursor = DBUtil.query(__db, rawQuery, false, null);
+    try {
+      final User _result;
+      if (_cursor.moveToFirst()) {
+        _result = __entityCursorConverter_fooBarUser(_cursor);
+      } else {
+        _result = null;
+      }
+      return _result;
+    } finally {
+      _cursor.close();
+    }
+  }
+
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+
+  private User __entityCursorConverter_fooBarUser(@NonNull final Cursor cursor) {
+    final User _entity;
+    final int _cursorIndexOfUid = CursorUtil.getColumnIndex(cursor, "uid");
+    final int _cursorIndexOfName = CursorUtil.getColumnIndex(cursor, "name");
+    final int _cursorIndexOfLastName = CursorUtil.getColumnIndex(cursor, "lastName");
+    final int _cursorIndexOfAge = CursorUtil.getColumnIndex(cursor, "ageColumn");
+    _entity = new User();
+    if (_cursorIndexOfUid != -1) {
+      _entity.uid = cursor.getInt(_cursorIndexOfUid);
+    }
+    if (_cursorIndexOfName != -1) {
+      _entity.name = cursor.getString(_cursorIndexOfName);
+    }
+    if (_cursorIndexOfLastName != -1) {
+      final String _tmpLastName;
+      _tmpLastName = cursor.getString(_cursorIndexOfLastName);
+      _entity.setLastName(_tmpLastName);
+    }
+    if (_cursorIndexOfAge != -1) {
+      _entity.age = cursor.getInt(_cursorIndexOfAge);
+    }
+    return _entity;
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
index 6228fe0..4d0654d 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
@@ -8,7 +8,6 @@
 import androidx.room.util.DBUtil;
 import androidx.room.util.SQLiteConnectionUtil;
 import androidx.room.util.StringUtil;
-import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
 import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
@@ -26,386 +25,337 @@
 import java.util.List;
 import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
 public final class DeletionDao_Impl implements DeletionDao {
-    private final RoomDatabase __db;
+  private final RoomDatabase __db;
 
-    private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
+  private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
+  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
 
-    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
 
-    private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
+  private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
 
-    public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `User` WHERE `uid` = ?";
-            }
+  public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-            }
-        };
-        this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `User` WHERE `uid` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-            }
-        };
-        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement,
+          @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+      }
+    };
+    this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement,
-                    @NonNull final MultiPKeyEntity entity) {
-                statement.bindText(1, entity.name);
-                statement.bindText(2, entity.lastName);
-            }
-        };
-        this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "DELETE FROM `Book` WHERE `bookId` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement,
+          @NonNull final MultiPKeyEntity entity) {
+        statement.bindText(1, entity.name);
+        statement.bindText(2, entity.lastName);
+      }
+    };
+    this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "DELETE FROM `Book` WHERE `bookId` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
-                statement.bindLong(1, entity.bookId);
-            }
-        };
-    }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
+        statement.bindLong(1, entity.bookId);
+      }
+    };
+  }
 
-    @Override
-    public void deleteUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void deleteUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      return null;
+    });
+  }
 
-    @Override
-    public void deleteUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user1);
-                __deleteAdapterOfUser.handleMultiple(_connection, others);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void deleteUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user1);
+      __deleteAdapterOfUser.handleMultiple(_connection, others);
+      return null;
+    });
+  }
 
-    @Override
-    public void deleteArrayOfUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handleMultiple(_connection, users);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void deleteArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handleMultiple(_connection, users);
+      return null;
+    });
+  }
 
-    @Override
-    public Integer deleteUserAndReturnCountObject(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public Integer deleteUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
 
-    @Override
-    public int deleteUserAndReturnCount(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int deleteUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
 
-    @Override
-    public int deleteUserAndReturnCount(final User user1, final List<User> others) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handle(_connection, user1);
-                _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int deleteUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user1);
+      _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
+      return _result;
+    });
+  }
 
-    @Override
-    public int deleteUserAndReturnCount(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int deleteUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
+      return _result;
+    });
+  }
 
-    @Override
-    public Completable deleteUserCompletable(final User user) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                __db.beginTransaction();
-                try {
-                    __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Completable deleteUserCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Single<Integer> deleteUserSingle(final User user) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Single<Integer> deleteUserSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Maybe<Integer> deleteUserMaybe(final User user) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __deleteCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Maybe<Integer> deleteUserMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __deleteCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public int multiPKey(final MultiPKeyEntity entity) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
+      return _result;
+    });
+  }
 
-    @Override
-    public void deleteUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __deleteAdapterOfUser.handle(_connection, user);
-                __deleteAdapterOfBook.handle(_connection, book);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void deleteUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      __deleteAdapterOfBook.handle(_connection, book);
+      return null;
+    });
+  }
 
-    @Override
-    public int deleteByUid(final int uid) {
+  @Override
+  public int deleteByUid(final int uid) {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
+
+  @Override
+  public Completable deleteByUidCompletable(final int uid) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
         final String _sql = "DELETE FROM user where uid = ?";
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, uid);
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Completable deleteByUidCompletable(final int uid) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Single<Integer> deleteByUidSingle(final int uid) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Single<Integer> deleteByUidSingle(final int uid) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Maybe<Integer> deleteByUidMaybe(final int uid) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "DELETE FROM user where uid = ?";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, uid);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Maybe<Integer> deleteByUidMaybe(final int uid) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "DELETE FROM user where uid = ?";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                int _argIndex = 1;
-                _stmt.bindLong(_argIndex, uid);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public int deleteByUidList(final int... uid) {
+    final StringBuilder _stringBuilder = new StringBuilder();
+    _stringBuilder.append("DELETE FROM user where uid IN(");
+    final int _inputSize = uid == null ? 1 : uid.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (uid == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : uid) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
+          }
+        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public int deleteByUidList(final int... uid) {
-        final StringBuilder _stringBuilder = new StringBuilder();
-        _stringBuilder.append("DELETE FROM user where uid IN(");
-        final int _inputSize = uid == null ? 1 : uid.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    if (uid == null) {
-                        _stmt.bindNull(_argIndex);
-                    } else {
-                        for (int _item : uid) {
-                            _stmt.bindLong(_argIndex, _item);
-                            _argIndex++;
-                        }
-                    }
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
+  @Override
+  public int deleteEverything() {
+    final String _sql = "DELETE FROM user";
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public int deleteEverything() {
-        final String _sql = "DELETE FROM user";
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    _stmt.step();
-                    return SQLiteConnectionUtil.getTotalChangedRows(_connection);
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
index afbf5da..49a5436 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
@@ -6,7 +6,6 @@
 import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
 import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
@@ -23,400 +22,342 @@
 import java.util.List;
 import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
 public final class UpdateDao_Impl implements UpdateDao {
-    private final RoomDatabase __db;
+  private final RoomDatabase __db;
 
-    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
 
-    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
+  private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-    private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
+  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
 
-    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+  private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
 
-    private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
+  private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
 
-    public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
+  public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SupportSQLiteStatement statement,
+          @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindString(2, entity.name);
+        statement.bindString(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    };
+    this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement,
-                    @NonNull final MultiPKeyEntity entity) {
-                statement.bindText(1, entity.name);
-                statement.bindText(2, entity.lastName);
-                statement.bindText(3, entity.name);
-                statement.bindText(4, entity.lastName);
-            }
-        };
-        this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement,
+          @NonNull final MultiPKeyEntity entity) {
+        statement.bindText(1, entity.name);
+        statement.bindText(2, entity.lastName);
+        statement.bindText(3, entity.name);
+        statement.bindText(4, entity.lastName);
+      }
+    };
+    this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-                statement.bindLong(3, entity.bookId);
-            }
-        };
-    }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    };
+  }
 
-    @Override
-    public void updateUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void updateUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      return null;
+    });
+  }
 
-    @Override
-    public void updateUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user1);
-                __updateAdapterOfUser.handleMultiple(_connection, others);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void updateUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user1);
+      __updateAdapterOfUser.handleMultiple(_connection, others);
+      return null;
+    });
+  }
 
-    @Override
-    public void updateArrayOfUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handleMultiple(_connection, users);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void updateArrayOfUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handleMultiple(_connection, users);
+      return null;
+    });
+  }
 
-    @Override
-    public void updateTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser_1.handle(_connection, userOne);
-                __updateAdapterOfUser_1.handle(_connection, userTwo);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void updateTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser_1.handle(_connection, userOne);
+      __updateAdapterOfUser_1.handle(_connection, userTwo);
+      return null;
+    });
+  }
 
-    @Override
-    public int updateUserAndReturnCount(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int updateUserAndReturnCount(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
 
-    @Override
-    public int updateUserAndReturnCount(final User user1, final List<User> others) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user1);
-                _result += __updateAdapterOfUser.handleMultiple(_connection, others);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int updateUserAndReturnCount(final User user1, final List<User> others) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user1);
+      _result += __updateAdapterOfUser.handleMultiple(_connection, others);
+      return _result;
+    });
+  }
 
-    @Override
-    public int updateUserAndReturnCount(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handleMultiple(_connection, users);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int updateUserAndReturnCount(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handleMultiple(_connection, users);
+      return _result;
+    });
+  }
 
-    @Override
-    public Integer updateUserAndReturnCountObject(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfUser.handle(_connection, user);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public Integer updateUserAndReturnCountObject(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
+    });
+  }
 
-    @Override
-    public Completable updateUserAndReturnCountCompletable(final User user) {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                __db.beginTransaction();
-                try {
-                    __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Completable updateUserAndReturnCountCompletable(final User user) {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
+        __db.beginTransaction();
+        try {
+          __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Single<Integer> updateUserAndReturnCountSingle(final User user) {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Single<Integer> updateUserAndReturnCountSingle(final User user) {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                int _total = 0;
-                __db.beginTransaction();
-                try {
-                    _total += __updateCompatAdapterOfUser.handle(user);
-                    __db.setTransactionSuccessful();
-                    return _total;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        int _total = 0;
+        __db.beginTransaction();
+        try {
+          _total += __updateCompatAdapterOfUser.handle(user);
+          __db.setTransactionSuccessful();
+          return _total;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public int multiPKey(final MultiPKeyEntity entity) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
-            @Override
-            @NonNull
-            public Integer invoke(@NonNull final SQLiteConnection _connection) {
-                int _result = 0;
-                _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
-                return _result;
-            }
-        });
-    }
+  @Override
+  public int multiPKey(final MultiPKeyEntity entity) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
+      return _result;
+    });
+  }
 
-    @Override
-    public void updateUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __updateAdapterOfUser.handle(_connection, user);
-                __updateAdapterOfBook.handle(_connection, book);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void updateUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      __updateAdapterOfBook.handle(_connection, book);
+      return null;
+    });
+  }
 
-    @Override
-    public void updateAndAge(final User user) {
-        DBUtil.performBlocking(__db, false, true, (_connection) -> {
-            UpdateDao.super.updateAndAge(user);
-            return Unit.INSTANCE;
-        });
-    }
+  @Override
+  public void ageUserByUid(final String uid) {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindText(_argIndex, uid);
+        _stmt.step();
+        return null;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public void ageUserByUid(final String uid) {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    int _argIndex = 1;
-                    _stmt.bindText(_argIndex, uid);
-                    _stmt.step();
-                    return null;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
+  @Override
+  public void ageUserAll() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return null;
+      } finally {
+        _stmt.close();
+      }
+    });
+  }
 
-    @Override
-    public void ageUserAll() {
+  @Override
+  public Completable ageUserAllCompletable() {
+    return Completable.fromCallable(new Callable<Void>() {
+      @Override
+      @Nullable
+      public Void call() throws Exception {
         final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                final SQLiteStatement _stmt = _connection.prepare(_sql);
-                try {
-                    _stmt.step();
-                    return null;
-                } finally {
-                    _stmt.close();
-                }
-            }
-        });
-    }
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return null;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Completable ageUserAllCompletable() {
-        return Completable.fromCallable(new Callable<Void>() {
-            @Override
-            @Nullable
-            public Void call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return null;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Single<Integer> ageUserAllSingle() {
+    return Single.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Single<Integer> ageUserAllSingle() {
-        return Single.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
+  @Override
+  public Maybe<Integer> ageUserAllMaybe() {
+    return Maybe.fromCallable(new Callable<Integer>() {
+      @Override
+      @Nullable
+      public Integer call() throws Exception {
+        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+        __db.beginTransaction();
+        try {
+          final Integer _result = _stmt.executeUpdateDelete();
+          __db.setTransactionSuccessful();
+          return _result;
+        } finally {
+          __db.endTransaction();
+        }
+      }
+    });
+  }
 
-    @Override
-    public Maybe<Integer> ageUserAllMaybe() {
-        return Maybe.fromCallable(new Callable<Integer>() {
-            @Override
-            @Nullable
-            public Integer call() throws Exception {
-                final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-                final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-                __db.beginTransaction();
-                try {
-                    final Integer _result = _stmt.executeUpdateDelete();
-                    __db.setTransactionSuccessful();
-                    return _result;
-                } finally {
-                    __db.endTransaction();
-                }
-            }
-        });
-    }
-
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
index 0858d17..a3f22bb 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
@@ -6,175 +6,143 @@
 import androidx.room.EntityUpsertAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
-import java.lang.Long;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
 public final class UpsertDao_Impl implements UpsertDao {
-    private final RoomDatabase __db;
+  private final RoomDatabase __db;
 
-    private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
+  private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
 
-    private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
+  private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
 
-    public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
+  public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-            }
-        }, new EntityDeleteOrUpdateAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+      }
+    }, new EntityDeleteOrUpdateAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        });
-        this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+        statement.bindLong(5, entity.uid);
+      }
+    });
+    this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-            }
-        }, new EntityDeleteOrUpdateAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    }, new EntityDeleteOrUpdateAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "UPDATE `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-                statement.bindLong(3, entity.bookId);
-            }
-        });
-    }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+        statement.bindLong(3, entity.bookId);
+      }
+    });
+  }
 
-    @Override
-    public void upsertUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void upsertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user);
+      return null;
+    });
+  }
 
-    @Override
-    public void upsertUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user1);
-                __upsertAdapterOfUser.upsert(_connection, others);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void upsertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user1);
+      __upsertAdapterOfUser.upsert(_connection, others);
+      return null;
+    });
+  }
 
-    @Override
-    public void upsertUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, users);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void upsertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, users);
+      return null;
+    });
+  }
 
-    @Override
-    public void upsertTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, userOne);
-                __upsertAdapterOfUser.upsert(_connection, userTwo);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void upsertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, userOne);
+      __upsertAdapterOfUser.upsert(_connection, userTwo);
+      return null;
+    });
+  }
 
-    @Override
-    public void upsertUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __upsertAdapterOfUser.upsert(_connection, user);
-                __upsertAdapterOfBook.upsert(_connection, book);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void upsertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __upsertAdapterOfUser.upsert(_connection, user);
+      __upsertAdapterOfBook.upsert(_connection, book);
+      return null;
+    });
+  }
 
-    @Override
-    public long upsertAndReturnId(final User user) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Long>() {
-            @Override
-            @NonNull
-            public Long invoke(@NonNull final SQLiteConnection _connection) {
-                return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
-            }
-        });
-    }
+  @Override
+  public long upsertAndReturnId(final User user) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
+    });
+  }
 
-    @Override
-    public long[] upsertAndReturnIdsArray(final User[] users) {
-        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, long[]>() {
-            @Override
-            @NonNull
-            public long[] invoke(@NonNull final SQLiteConnection _connection) {
-                return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
-            }
-        });
-    }
+  @Override
+  public long[] upsertAndReturnIdsArray(final User[] users) {
+    return DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
+    });
+  }
 
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
index 80b3ed3..2a4d60d 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
@@ -4,158 +4,135 @@
 import androidx.room.EntityInsertAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
-import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
-import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
 public final class WriterDao_Impl implements WriterDao {
-    private final RoomDatabase __db;
+  private final RoomDatabase __db;
 
-    private final EntityInsertAdapter<User> __insertAdapterOfUser;
+  private final EntityInsertAdapter<User> __insertAdapterOfUser;
 
-    private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
 
-    private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
+  private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
 
-    private final EntityInsertAdapter<Book> __insertAdapterOfBook;
+  private final EntityInsertAdapter<Book> __insertAdapterOfBook;
 
-    public WriterDao_Impl(@NonNull final RoomDatabase __db) {
-        this.__db = __db;
-        this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR ABORT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
+  public WriterDao_Impl(@NonNull final RoomDatabase __db) {
+    this.__db = __db;
+    this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR REPLACE INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR REPLACE INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT INTO `User` (`uid`,`name`,`lastName`,`ageColumn`) VALUES (?,?,?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindText(2, entity.name);
-                statement.bindText(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-            }
-        };
-        this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "INSERT OR ABORT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
-            }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+        statement.bindLong(1, entity.uid);
+        statement.bindText(2, entity.name);
+        statement.bindText(3, entity.getLastName());
+        statement.bindLong(4, entity.age);
+      }
+    };
+    this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
+      @Override
+      @NonNull
+      protected String createQuery() {
+        return "INSERT OR ABORT INTO `Book` (`bookId`,`uid`) VALUES (?,?)";
+      }
 
-            @Override
-            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
-                statement.bindLong(1, entity.bookId);
-                statement.bindLong(2, entity.uid);
-            }
-        };
-    }
+      @Override
+      protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
+        statement.bindLong(1, entity.bookId);
+        statement.bindLong(2, entity.uid);
+      }
+    };
+  }
 
-    @Override
-    public void insertUser(final User user) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void insertUser(final User user) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user);
+      return null;
+    });
+  }
 
-    @Override
-    public void insertUsers(final User user1, final List<User> others) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user1);
-                __insertAdapterOfUser.insert(_connection, others);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void insertUsers(final User user1, final List<User> others) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user1);
+      __insertAdapterOfUser.insert(_connection, others);
+      return null;
+    });
+  }
 
-    @Override
-    public void insertUsers(final User[] users) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser_1.insert(_connection, users);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void insertUsers(final User[] users) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser_1.insert(_connection, users);
+      return null;
+    });
+  }
 
-    @Override
-    public void insertTwoUsers(final User userOne, final User userTwo) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser_2.insert(_connection, userOne);
-                __insertAdapterOfUser_2.insert(_connection, userTwo);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void insertTwoUsers(final User userOne, final User userTwo) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser_2.insert(_connection, userOne);
+      __insertAdapterOfUser_2.insert(_connection, userTwo);
+      return null;
+    });
+  }
 
-    @Override
-    public void insertUserAndBook(final User user, final Book book) {
-        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
-            @Override
-            @NonNull
-            public Void invoke(@NonNull final SQLiteConnection _connection) {
-                __insertAdapterOfUser.insert(_connection, user);
-                __insertAdapterOfBook.insert(_connection, book);
-                return null;
-            }
-        });
-    }
+  @Override
+  public void insertUserAndBook(final User user, final Book book) {
+    DBUtil.performBlocking(__db, false, true, (_connection) -> {
+      __insertAdapterOfUser.insert(_connection, user);
+      __insertAdapterOfBook.insert(_connection, book);
+      return null;
+    });
+  }
 
-    @NonNull
-    public static List<Class<?>> getRequiredConverters() {
-        return Collections.emptyList();
-    }
-}
\ No newline at end of file
+  @NonNull
+  public static List<Class<?>> getRequiredConverters() {
+    return Collections.emptyList();
+  }
+}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
index 57c2e48..c03a592 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
@@ -1,21 +1,22 @@
-import android.database.Cursor
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndex
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import androidx.room.util.recursiveFetchHashMap
-import java.util.ArrayList
-import java.util.HashMap
+import androidx.room.util.performBlocking
+import androidx.room.util.recursiveFetchMap
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.MutableList
+import kotlin.collections.MutableMap
 import kotlin.collections.Set
+import kotlin.collections.mutableListOf
+import kotlin.collections.mutableMapOf
 import kotlin.reflect.KClass
 import kotlin.text.StringBuilder
 
@@ -31,130 +32,128 @@
 
   public override fun getSongsWithArtist(): SongWithArtist {
     val _sql: String = "SELECT * FROM Song"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_cursor, "songId")
-      val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_cursor, "artistKey")
-      val _collectionArtist: HashMap<Long, Artist?> = HashMap<Long, Artist?>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _collectionArtist.put(_tmpKey, null)
-      }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipArtistAsArtist(_collectionArtist)
-      val _result: SongWithArtist
-      if (_cursor.moveToFirst()) {
-        val _tmpSong: Song
-        val _tmpSongId: Long
-        _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
-        val _tmpArtistKey: Long
-        _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpSong = Song(_tmpSongId,_tmpArtistKey)
-        val _tmpArtist: Artist?
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpArtist = _collectionArtist.get(_tmpKey_1)
-        if (_tmpArtist == null) {
-          error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _collectionArtist: MutableMap<Long, Artist?> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _collectionArtist.put(_tmpKey, null)
         }
-        _result = SongWithArtist(_tmpSong,_tmpArtist)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        _stmt.reset()
+        __fetchRelationshipArtistAsArtist(_connection, _collectionArtist)
+        val _result: SongWithArtist
+        if (_stmt.step()) {
+          val _tmpSong: Song
+          val _tmpSongId: Long
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
+          val _tmpArtistKey: Long
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpSong = Song(_tmpSongId,_tmpArtistKey)
+          val _tmpArtist: Artist?
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpArtist = _collectionArtist.get(_tmpKey_1)
+          if (_tmpArtist == null) {
+            error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+          }
+          _result = SongWithArtist(_tmpSong,_tmpArtist)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getArtistAndSongs(): ArtistAndSongs {
     val _sql: String = "SELECT * FROM Artist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_cursor, "artistId")
-      val _collectionSongs: HashMap<Long, ArrayList<Song>> = HashMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_stmt, "artistId")
+        val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong(_connection, _collectionSongs)
+        val _result: ArtistAndSongs
+        if (_stmt.step()) {
+          val _tmpArtist: Artist
+          val _tmpArtistId: Long
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpArtist = Artist(_tmpArtistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong(_collectionSongs)
-      val _result: ArtistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpArtist: Artist
-        val _tmpArtistId: Long
-        _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpArtist = Artist(_tmpArtistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getPlaylistAndSongs(): PlaylistAndSongs {
     val _sql: String = "SELECT * FROM Playlist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_cursor, "playlistId")
-      val _collectionSongs: HashMap<Long, ArrayList<Song>> = HashMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfPlaylistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_stmt, "playlistId")
+        val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfPlaylistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong_1(_connection, _collectionSongs)
+        val _result: PlaylistAndSongs
+        if (_stmt.step()) {
+          val _tmpPlaylist: Playlist
+          val _tmpPlaylistId: Long
+          _tmpPlaylistId = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpPlaylist = Playlist(_tmpPlaylistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong_1(_collectionSongs)
-      val _result: PlaylistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpPlaylist: Playlist
-        val _tmpPlaylistId: Long
-        _tmpPlaylistId = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpPlaylist = Playlist(_tmpPlaylistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
-  private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>) {
+  private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection,
+      _map: MutableMap<Long, Artist?>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, false) {
-        __fetchRelationshipArtistAsArtist(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, false) { _tmpMap ->
+        __fetchRelationshipArtistAsArtist(_connection, _tmpMap)
       }
       return
     }
@@ -164,44 +163,43 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistId")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfArtistId: Int = 0
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
         if (_map.containsKey(_tmpKey)) {
           val _item_1: Artist
           val _tmpArtistId: Long
-          _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
           _item_1 = Artist(_tmpArtistId)
           _map.put(_tmpKey, _item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong(_connection: SQLiteConnection,
+      _map: MutableMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, true) {
-        __fetchRelationshipSongAsSong(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong(_connection, _tmpMap)
       }
       return
     }
@@ -211,48 +209,47 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistKey")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistKey")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong_1(_connection: SQLiteConnection,
+      _map: MutableMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, true) {
-        __fetchRelationshipSongAsSong_1(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong_1(_connection, _tmpMap)
       }
       return
     }
@@ -262,14 +259,12 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
       // _junction.playlistKey
       val _itemKeyIndex: Int = 2
@@ -278,22 +273,22 @@
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
index 0c5dcc4..6a397c8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
@@ -1,21 +1,21 @@
-import android.database.Cursor
 import androidx.collection.ArrayMap
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndex
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.room.util.performBlocking
 import androidx.room.util.recursiveFetchArrayMap
-import java.util.ArrayList
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.MutableList
 import kotlin.collections.Set
+import kotlin.collections.mutableListOf
 import kotlin.reflect.KClass
 import kotlin.text.StringBuilder
 
@@ -31,130 +31,130 @@
 
   public override fun getSongsWithArtist(): SongWithArtist {
     val _sql: String = "SELECT * FROM Song"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_cursor, "songId")
-      val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_cursor, "artistKey")
-      val _collectionArtist: ArrayMap<Long, Artist?> = ArrayMap<Long, Artist?>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _collectionArtist.put(_tmpKey, null)
-      }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipArtistAsArtist(_collectionArtist)
-      val _result: SongWithArtist
-      if (_cursor.moveToFirst()) {
-        val _tmpSong: Song
-        val _tmpSongId: Long
-        _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
-        val _tmpArtistKey: Long
-        _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpSong = Song(_tmpSongId,_tmpArtistKey)
-        val _tmpArtist: Artist?
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpArtist = _collectionArtist.get(_tmpKey_1)
-        if (_tmpArtist == null) {
-          error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _collectionArtist: ArrayMap<Long, Artist?> = ArrayMap<Long, Artist?>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _collectionArtist.put(_tmpKey, null)
         }
-        _result = SongWithArtist(_tmpSong,_tmpArtist)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        _stmt.reset()
+        __fetchRelationshipArtistAsArtist(_connection, _collectionArtist)
+        val _result: SongWithArtist
+        if (_stmt.step()) {
+          val _tmpSong: Song
+          val _tmpSongId: Long
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
+          val _tmpArtistKey: Long
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpSong = Song(_tmpSongId,_tmpArtistKey)
+          val _tmpArtist: Artist?
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpArtist = _collectionArtist.get(_tmpKey_1)
+          if (_tmpArtist == null) {
+            error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+          }
+          _result = SongWithArtist(_tmpSong,_tmpArtist)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getArtistAndSongs(): ArtistAndSongs {
     val _sql: String = "SELECT * FROM Artist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_cursor, "artistId")
-      val _collectionSongs: ArrayMap<Long, ArrayList<Song>> = ArrayMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_stmt, "artistId")
+        val _collectionSongs: ArrayMap<Long, MutableList<Song>> =
+            ArrayMap<Long, MutableList<Song>>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong(_connection, _collectionSongs)
+        val _result: ArtistAndSongs
+        if (_stmt.step()) {
+          val _tmpArtist: Artist
+          val _tmpArtistId: Long
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpArtist = Artist(_tmpArtistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong(_collectionSongs)
-      val _result: ArtistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpArtist: Artist
-        val _tmpArtistId: Long
-        _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpArtist = Artist(_tmpArtistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getPlaylistAndSongs(): PlaylistAndSongs {
     val _sql: String = "SELECT * FROM Playlist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_cursor, "playlistId")
-      val _collectionSongs: ArrayMap<Long, ArrayList<Song>> = ArrayMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfPlaylistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_stmt, "playlistId")
+        val _collectionSongs: ArrayMap<Long, MutableList<Song>> =
+            ArrayMap<Long, MutableList<Song>>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfPlaylistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong_1(_connection, _collectionSongs)
+        val _result: PlaylistAndSongs
+        if (_stmt.step()) {
+          val _tmpPlaylist: Playlist
+          val _tmpPlaylistId: Long
+          _tmpPlaylistId = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpPlaylist = Playlist(_tmpPlaylistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong_1(_collectionSongs)
-      val _result: PlaylistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpPlaylist: Playlist
-        val _tmpPlaylistId: Long
-        _tmpPlaylistId = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpPlaylist = Playlist(_tmpPlaylistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
-  private fun __fetchRelationshipArtistAsArtist(_map: ArrayMap<Long, Artist?>) {
+  private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection,
+      _map: ArrayMap<Long, Artist?>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchArrayMap(_map, false) {
-        __fetchRelationshipArtistAsArtist(it)
+    if (_map.size > 999) {
+      recursiveFetchArrayMap(_map, false) { _tmpMap ->
+        __fetchRelationshipArtistAsArtist(_connection, _tmpMap)
       }
       return
     }
@@ -164,44 +164,43 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistId")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfArtistId: Int = 0
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
         if (_map.containsKey(_tmpKey)) {
           val _item_1: Artist
           val _tmpArtistId: Long
-          _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
           _item_1 = Artist(_tmpArtistId)
           _map.put(_tmpKey, _item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong(_map: ArrayMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong(_connection: SQLiteConnection,
+      _map: ArrayMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchArrayMap(_map, true) {
-        __fetchRelationshipSongAsSong(it)
+    if (_map.size > 999) {
+      recursiveFetchArrayMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong(_connection, _tmpMap)
       }
       return
     }
@@ -211,48 +210,47 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistKey")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistKey")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong_1(_map: ArrayMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong_1(_connection: SQLiteConnection,
+      _map: ArrayMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchArrayMap(_map, true) {
-        __fetchRelationshipSongAsSong_1(it)
+    if (_map.size > 999) {
+      recursiveFetchArrayMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong_1(_connection, _tmpMap)
       }
       return
     }
@@ -262,14 +260,12 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
       // _junction.playlistKey
       val _itemKeyIndex: Int = 2
@@ -278,22 +274,22 @@
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
index f6480e9c..c843aef 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
@@ -1,14 +1,12 @@
-import android.database.Cursor
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndex
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import androidx.room.util.recursiveFetchHashMap
+import androidx.room.util.performBlocking
+import androidx.room.util.recursiveFetchMap
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
 import java.nio.ByteBuffer
-import java.util.HashMap
 import javax.`annotation`.processing.Generated
 import kotlin.ByteArray
 import kotlin.Int
@@ -16,7 +14,9 @@
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.MutableMap
 import kotlin.collections.Set
+import kotlin.collections.mutableMapOf
 import kotlin.reflect.KClass
 import kotlin.text.StringBuilder
 
@@ -32,54 +32,54 @@
 
   public override fun getSongsWithArtist(): SongWithArtist {
     val _sql: String = "SELECT * FROM Song"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_cursor, "songId")
-      val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_cursor, "artistKey")
-      val _collectionArtist: HashMap<ByteBuffer, Artist?> = HashMap<ByteBuffer, Artist?>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: ByteBuffer
-        _tmpKey = ByteBuffer.wrap(_cursor.getBlob(_cursorIndexOfArtistKey))
-        _collectionArtist.put(_tmpKey, null)
-      }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipArtistAsArtist(_collectionArtist)
-      val _result: SongWithArtist
-      if (_cursor.moveToFirst()) {
-        val _tmpSong: Song
-        val _tmpSongId: Long
-        _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
-        val _tmpArtistKey: ByteArray
-        _tmpArtistKey = _cursor.getBlob(_cursorIndexOfArtistKey)
-        _tmpSong = Song(_tmpSongId,_tmpArtistKey)
-        val _tmpArtist: Artist?
-        val _tmpKey_1: ByteBuffer
-        _tmpKey_1 = ByteBuffer.wrap(_cursor.getBlob(_cursorIndexOfArtistKey))
-        _tmpArtist = _collectionArtist.get(_tmpKey_1)
-        if (_tmpArtist == null) {
-          error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _collectionArtist: MutableMap<ByteBuffer, Artist?> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: ByteBuffer
+          _tmpKey = ByteBuffer.wrap(_stmt.getBlob(_cursorIndexOfArtistKey))
+          _collectionArtist.put(_tmpKey, null)
         }
-        _result = SongWithArtist(_tmpSong,_tmpArtist)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        _stmt.reset()
+        __fetchRelationshipArtistAsArtist(_connection, _collectionArtist)
+        val _result: SongWithArtist
+        if (_stmt.step()) {
+          val _tmpSong: Song
+          val _tmpSongId: Long
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
+          val _tmpArtistKey: ByteArray
+          _tmpArtistKey = _stmt.getBlob(_cursorIndexOfArtistKey)
+          _tmpSong = Song(_tmpSongId,_tmpArtistKey)
+          val _tmpArtist: Artist?
+          val _tmpKey_1: ByteBuffer
+          _tmpKey_1 = ByteBuffer.wrap(_stmt.getBlob(_cursorIndexOfArtistKey))
+          _tmpArtist = _collectionArtist.get(_tmpKey_1)
+          if (_tmpArtist == null) {
+            error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+          }
+          _result = SongWithArtist(_tmpSong,_tmpArtist)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
-  private fun __fetchRelationshipArtistAsArtist(_map: HashMap<ByteBuffer, Artist?>) {
+  private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection,
+      _map: MutableMap<ByteBuffer, Artist?>) {
     val __mapKeySet: Set<ByteBuffer> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, false) {
-        __fetchRelationshipArtistAsArtist(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, false) { _tmpMap ->
+        __fetchRelationshipArtistAsArtist(_connection, _tmpMap)
       }
       return
     }
@@ -89,33 +89,31 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: ByteBuffer in __mapKeySet) {
       _stmt.bindBlob(_argIndex, _item.array())
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistId")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfArtistId: Int = 0
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: ByteBuffer
-        _tmpKey = ByteBuffer.wrap(_cursor.getBlob(_itemKeyIndex))
+        _tmpKey = ByteBuffer.wrap(_stmt.getBlob(_itemKeyIndex))
         if (_map.containsKey(_tmpKey)) {
           val _item_1: Artist
           val _tmpArtistId: ByteArray
-          _tmpArtistId = _cursor.getBlob(_cursorIndexOfArtistId)
+          _tmpArtistId = _stmt.getBlob(_cursorIndexOfArtistId)
           _item_1 = Artist(_tmpArtistId)
           _map.put(_tmpKey, _item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
index 2c0afeb..5096b2b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
@@ -1,20 +1,20 @@
-import android.database.Cursor
 import androidx.collection.LongSparseArray
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndex
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.room.util.performBlocking
 import androidx.room.util.recursiveFetchLongSparseArray
-import java.util.ArrayList
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.MutableList
+import kotlin.collections.mutableListOf
 import kotlin.reflect.KClass
 import kotlin.text.StringBuilder
 
@@ -30,129 +30,129 @@
 
   public override fun getSongsWithArtist(): SongWithArtist {
     val _sql: String = "SELECT * FROM Song"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_cursor, "songId")
-      val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_cursor, "artistKey")
-      val _collectionArtist: LongSparseArray<Artist?> = LongSparseArray<Artist?>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _collectionArtist.put(_tmpKey, null)
-      }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipArtistAsArtist(_collectionArtist)
-      val _result: SongWithArtist
-      if (_cursor.moveToFirst()) {
-        val _tmpSong: Song
-        val _tmpSongId: Long
-        _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
-        val _tmpArtistKey: Long
-        _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpSong = Song(_tmpSongId,_tmpArtistKey)
-        val _tmpArtist: Artist?
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistKey)
-        _tmpArtist = _collectionArtist.get(_tmpKey_1)
-        if (_tmpArtist == null) {
-          error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _collectionArtist: LongSparseArray<Artist?> = LongSparseArray<Artist?>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _collectionArtist.put(_tmpKey, null)
         }
-        _result = SongWithArtist(_tmpSong,_tmpArtist)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        _stmt.reset()
+        __fetchRelationshipArtistAsArtist(_connection, _collectionArtist)
+        val _result: SongWithArtist
+        if (_stmt.step()) {
+          val _tmpSong: Song
+          val _tmpSongId: Long
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
+          val _tmpArtistKey: Long
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpSong = Song(_tmpSongId,_tmpArtistKey)
+          val _tmpArtist: Artist?
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistKey)
+          _tmpArtist = _collectionArtist.get(_tmpKey_1)
+          if (_tmpArtist == null) {
+            error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.")
+          }
+          _result = SongWithArtist(_tmpSong,_tmpArtist)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getArtistAndSongs(): ArtistAndSongs {
     val _sql: String = "SELECT * FROM Artist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_cursor, "artistId")
-      val _collectionSongs: LongSparseArray<ArrayList<Song>> = LongSparseArray<ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_stmt, "artistId")
+        val _collectionSongs: LongSparseArray<MutableList<Song>> =
+            LongSparseArray<MutableList<Song>>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong(_connection, _collectionSongs)
+        val _result: ArtistAndSongs
+        if (_stmt.step()) {
+          val _tmpArtist: Artist
+          val _tmpArtistId: Long
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpArtist = Artist(_tmpArtistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpSongsCollection = checkNotNull(_collectionSongs.get(_tmpKey_1))
+          _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong(_collectionSongs)
-      val _result: ArtistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpArtist: Artist
-        val _tmpArtistId: Long
-        _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpArtist = Artist(_tmpArtistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpSongsCollection = checkNotNull(_collectionSongs.get(_tmpKey_1))
-        _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getPlaylistAndSongs(): PlaylistAndSongs {
     val _sql: String = "SELECT * FROM Playlist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_cursor, "playlistId")
-      val _collectionSongs: LongSparseArray<ArrayList<Song>> = LongSparseArray<ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfPlaylistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_stmt, "playlistId")
+        val _collectionSongs: LongSparseArray<MutableList<Song>> =
+            LongSparseArray<MutableList<Song>>()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfPlaylistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong_1(_connection, _collectionSongs)
+        val _result: PlaylistAndSongs
+        if (_stmt.step()) {
+          val _tmpPlaylist: Playlist
+          val _tmpPlaylistId: Long
+          _tmpPlaylistId = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpPlaylist = Playlist(_tmpPlaylistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpSongsCollection = checkNotNull(_collectionSongs.get(_tmpKey_1))
+          _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong_1(_collectionSongs)
-      val _result: PlaylistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpPlaylist: Playlist
-        val _tmpPlaylistId: Long
-        _tmpPlaylistId = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpPlaylist = Playlist(_tmpPlaylistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpSongsCollection = checkNotNull(_collectionSongs.get(_tmpKey_1))
-        _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
-  private fun __fetchRelationshipArtistAsArtist(_map: LongSparseArray<Artist?>) {
+  private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection,
+      _map: LongSparseArray<Artist?>) {
     if (_map.isEmpty()) {
       return
     }
-    if (_map.size() > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchLongSparseArray(_map, false) {
-        __fetchRelationshipArtistAsArtist(it)
+    if (_map.size() > 999) {
+      recursiveFetchLongSparseArray(_map, false) { _tmpMap ->
+        __fetchRelationshipArtistAsArtist(_connection, _tmpMap)
       }
       return
     }
@@ -162,44 +162,43 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (i in 0 until _map.size()) {
       val _item: Long = _map.keyAt(i)
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistId")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfArtistId: Int = 0
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
         if (_map.containsKey(_tmpKey)) {
           val _item_1: Artist
           val _tmpArtistId: Long
-          _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
           _item_1 = Artist(_tmpArtistId)
           _map.put(_tmpKey, _item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong(_map: LongSparseArray<ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong(_connection: SQLiteConnection,
+      _map: LongSparseArray<MutableList<Song>>) {
     if (_map.isEmpty()) {
       return
     }
-    if (_map.size() > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchLongSparseArray(_map, true) {
-        __fetchRelationshipSongAsSong(it)
+    if (_map.size() > 999) {
+      recursiveFetchLongSparseArray(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong(_connection, _tmpMap)
       }
       return
     }
@@ -209,48 +208,47 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (i in 0 until _map.size()) {
       val _item: Long = _map.keyAt(i)
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistKey")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistKey")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong_1(_map: LongSparseArray<ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong_1(_connection: SQLiteConnection,
+      _map: LongSparseArray<MutableList<Song>>) {
     if (_map.isEmpty()) {
       return
     }
-    if (_map.size() > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchLongSparseArray(_map, true) {
-        __fetchRelationshipSongAsSong_1(it)
+    if (_map.size() > 999) {
+      recursiveFetchLongSparseArray(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong_1(_connection, _tmpMap)
       }
       return
     }
@@ -260,15 +258,13 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (i in 0 until _map.size()) {
       val _item: Long = _map.keyAt(i)
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
       // _junction.playlistKey
       val _itemKeyIndex: Int = 2
@@ -277,22 +273,22 @@
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
index 37123cf2..015fc74 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
@@ -1,21 +1,22 @@
-import android.database.Cursor
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndex
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import androidx.room.util.recursiveFetchHashMap
-import java.util.ArrayList
-import java.util.HashMap
+import androidx.room.util.performBlocking
+import androidx.room.util.recursiveFetchMap
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.MutableList
+import kotlin.collections.MutableMap
 import kotlin.collections.Set
+import kotlin.collections.mutableListOf
+import kotlin.collections.mutableMapOf
 import kotlin.reflect.KClass
 import kotlin.text.StringBuilder
 
@@ -31,145 +32,143 @@
 
   public override fun getSongsWithArtist(): SongWithArtist {
     val _sql: String = "SELECT * FROM Song"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_cursor, "songId")
-      val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_cursor, "artistKey")
-      val _collectionArtist: HashMap<Long, Artist?> = HashMap<Long, Artist?>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long?
-        if (_cursor.isNull(_cursorIndexOfArtistKey)) {
-          _tmpKey = null
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _collectionArtist: MutableMap<Long, Artist?> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long?
+          if (_stmt.isNull(_cursorIndexOfArtistKey)) {
+            _tmpKey = null
+          } else {
+            _tmpKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          }
+          if (_tmpKey != null) {
+            _collectionArtist.put(_tmpKey, null)
+          }
+        }
+        _stmt.reset()
+        __fetchRelationshipArtistAsArtist(_connection, _collectionArtist)
+        val _result: SongWithArtist
+        if (_stmt.step()) {
+          val _tmpSong: Song
+          val _tmpSongId: Long
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
+          val _tmpArtistKey: Long?
+          if (_stmt.isNull(_cursorIndexOfArtistKey)) {
+            _tmpArtistKey = null
+          } else {
+            _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
+          }
+          _tmpSong = Song(_tmpSongId,_tmpArtistKey)
+          val _tmpArtist: Artist?
+          val _tmpKey_1: Long?
+          if (_stmt.isNull(_cursorIndexOfArtistKey)) {
+            _tmpKey_1 = null
+          } else {
+            _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistKey)
+          }
+          if (_tmpKey_1 != null) {
+            _tmpArtist = _collectionArtist.get(_tmpKey_1)
+          } else {
+            _tmpArtist = null
+          }
+          _result = SongWithArtist(_tmpSong,_tmpArtist)
         } else {
-          _tmpKey = _cursor.getLong(_cursorIndexOfArtistKey)
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
         }
-        if (_tmpKey != null) {
-          _collectionArtist.put(_tmpKey, null)
-        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipArtistAsArtist(_collectionArtist)
-      val _result: SongWithArtist
-      if (_cursor.moveToFirst()) {
-        val _tmpSong: Song
-        val _tmpSongId: Long
-        _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
-        val _tmpArtistKey: Long?
-        if (_cursor.isNull(_cursorIndexOfArtistKey)) {
-          _tmpArtistKey = null
-        } else {
-          _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
-        }
-        _tmpSong = Song(_tmpSongId,_tmpArtistKey)
-        val _tmpArtist: Artist?
-        val _tmpKey_1: Long?
-        if (_cursor.isNull(_cursorIndexOfArtistKey)) {
-          _tmpKey_1 = null
-        } else {
-          _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistKey)
-        }
-        if (_tmpKey_1 != null) {
-          _tmpArtist = _collectionArtist.get(_tmpKey_1)
-        } else {
-          _tmpArtist = null
-        }
-        _result = SongWithArtist(_tmpSong,_tmpArtist)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <SongWithArtist>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getArtistAndSongs(): ArtistAndSongs {
     val _sql: String = "SELECT * FROM Artist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_cursor, "artistId")
-      val _collectionSongs: HashMap<Long, ArrayList<Song>> = HashMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfArtistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(_stmt, "artistId")
+        val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfArtistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong(_connection, _collectionSongs)
+        val _result: ArtistAndSongs
+        if (_stmt.step()) {
+          val _tmpArtist: Artist
+          val _tmpArtistId: Long
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpArtist = Artist(_tmpArtistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfArtistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong(_collectionSongs)
-      val _result: ArtistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpArtist: Artist
-        val _tmpArtistId: Long
-        _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpArtist = Artist(_tmpArtistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfArtistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = ArtistAndSongs(_tmpArtist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <ArtistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
   public override fun getPlaylistAndSongs(): PlaylistAndSongs {
     val _sql: String = "SELECT * FROM Playlist"
-    val _statement: RoomSQLiteQuery = acquire(_sql, 0)
-    __db.assertNotSuspendingTransaction()
-    val _cursor: Cursor = query(__db, _statement, true, null)
-    try {
-      val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_cursor, "playlistId")
-      val _collectionSongs: HashMap<Long, ArrayList<Song>> = HashMap<Long, ArrayList<Song>>()
-      while (_cursor.moveToNext()) {
-        val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_cursorIndexOfPlaylistId)
-        if (!_collectionSongs.containsKey(_tmpKey)) {
-          _collectionSongs.put(_tmpKey, ArrayList<Song>())
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(_stmt, "playlistId")
+        val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf()
+        while (_stmt.step()) {
+          val _tmpKey: Long
+          _tmpKey = _stmt.getLong(_cursorIndexOfPlaylistId)
+          if (!_collectionSongs.containsKey(_tmpKey)) {
+            _collectionSongs.put(_tmpKey, mutableListOf())
+          }
         }
+        _stmt.reset()
+        __fetchRelationshipSongAsSong_1(_connection, _collectionSongs)
+        val _result: PlaylistAndSongs
+        if (_stmt.step()) {
+          val _tmpPlaylist: Playlist
+          val _tmpPlaylistId: Long
+          _tmpPlaylistId = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpPlaylist = Playlist(_tmpPlaylistId)
+          val _tmpSongsCollection: MutableList<Song>
+          val _tmpKey_1: Long
+          _tmpKey_1 = _stmt.getLong(_cursorIndexOfPlaylistId)
+          _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
+          _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-      _cursor.moveToPosition(-1)
-      __fetchRelationshipSongAsSong_1(_collectionSongs)
-      val _result: PlaylistAndSongs
-      if (_cursor.moveToFirst()) {
-        val _tmpPlaylist: Playlist
-        val _tmpPlaylistId: Long
-        _tmpPlaylistId = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpPlaylist = Playlist(_tmpPlaylistId)
-        val _tmpSongsCollection: ArrayList<Song>
-        val _tmpKey_1: Long
-        _tmpKey_1 = _cursor.getLong(_cursorIndexOfPlaylistId)
-        _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1)
-        _result = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection)
-      } else {
-        error("The query result was empty, but expected a single row to return a NON-NULL object of type <PlaylistAndSongs>.")
-      }
-      return _result
-    } finally {
-      _cursor.close()
-      _statement.release()
     }
   }
 
-  private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>) {
+  private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection,
+      _map: MutableMap<Long, Artist?>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, false) {
-        __fetchRelationshipArtistAsArtist(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, false) { _tmpMap ->
+        __fetchRelationshipArtistAsArtist(_connection, _tmpMap)
       }
       return
     }
@@ -179,44 +178,43 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistId")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfArtistId: Int = 0
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
         if (_map.containsKey(_tmpKey)) {
           val _item_1: Artist?
           val _tmpArtistId: Long
-          _tmpArtistId = _cursor.getLong(_cursorIndexOfArtistId)
+          _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId)
           _item_1 = Artist(_tmpArtistId)
           _map.put(_tmpKey, _item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong(_connection: SQLiteConnection,
+      _map: MutableMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, true) {
-        __fetchRelationshipSongAsSong(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong(_connection, _tmpMap)
       }
       return
     }
@@ -226,39 +224,37 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
-      val _itemKeyIndex: Int = getColumnIndex(_cursor, "artistKey")
+      val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistKey")
       if (_itemKeyIndex == -1) {
         return
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long?
-        if (_cursor.isNull(_itemKeyIndex)) {
+        if (_stmt.isNull(_itemKeyIndex)) {
           _tmpKey = null
         } else {
-          _tmpKey = _cursor.getLong(_itemKeyIndex)
+          _tmpKey = _stmt.getLong(_itemKeyIndex)
         }
         if (_tmpKey != null) {
-          val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+          val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
           if (_tmpRelation != null) {
             val _item_1: Song
             val _tmpSongId: Long
-            _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+            _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
             val _tmpArtistKey: Long?
-            if (_cursor.isNull(_cursorIndexOfArtistKey)) {
+            if (_stmt.isNull(_cursorIndexOfArtistKey)) {
               _tmpArtistKey = null
             } else {
-              _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+              _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
             }
             _item_1 = Song(_tmpSongId,_tmpArtistKey)
             _tmpRelation.add(_item_1)
@@ -266,18 +262,19 @@
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
-  private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>) {
+  private fun __fetchRelationshipSongAsSong_1(_connection: SQLiteConnection,
+      _map: MutableMap<Long, MutableList<Song>>) {
     val __mapKeySet: Set<Long> = _map.keys
     if (__mapKeySet.isEmpty()) {
       return
     }
-    if (_map.size > RoomDatabase.MAX_BIND_PARAMETER_CNT) {
-      recursiveFetchHashMap(_map, true) {
-        __fetchRelationshipSongAsSong_1(it)
+    if (_map.size > 999) {
+      recursiveFetchMap(_map, true) { _tmpMap ->
+        __fetchRelationshipSongAsSong_1(_connection, _tmpMap)
       }
       return
     }
@@ -287,14 +284,12 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _stmt: RoomSQLiteQuery = acquire(_sql, _argCount)
+    val _stmt: SQLiteStatement = _connection.prepare(_sql)
     var _argIndex: Int = 1
     for (_item: Long in __mapKeySet) {
       _stmt.bindLong(_argIndex, _item)
       _argIndex++
     }
-    val _cursor: Cursor = query(__db, _stmt, false, null)
     try {
       // _junction.playlistKey
       val _itemKeyIndex: Int = 2
@@ -303,26 +298,26 @@
       }
       val _cursorIndexOfSongId: Int = 0
       val _cursorIndexOfArtistKey: Int = 1
-      while (_cursor.moveToNext()) {
+      while (_stmt.step()) {
         val _tmpKey: Long
-        _tmpKey = _cursor.getLong(_itemKeyIndex)
-        val _tmpRelation: ArrayList<Song>? = _map.get(_tmpKey)
+        _tmpKey = _stmt.getLong(_itemKeyIndex)
+        val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey)
         if (_tmpRelation != null) {
           val _item_1: Song
           val _tmpSongId: Long
-          _tmpSongId = _cursor.getLong(_cursorIndexOfSongId)
+          _tmpSongId = _stmt.getLong(_cursorIndexOfSongId)
           val _tmpArtistKey: Long?
-          if (_cursor.isNull(_cursorIndexOfArtistKey)) {
+          if (_stmt.isNull(_cursorIndexOfArtistKey)) {
             _tmpArtistKey = null
           } else {
-            _tmpArtistKey = _cursor.getLong(_cursorIndexOfArtistKey)
+            _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey)
           }
           _item_1 = Song(_tmpSongId,_tmpArtistKey)
           _tmpRelation.add(_item_1)
         }
       }
     } finally {
-      _cursor.close()
+      _stmt.close()
     }
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
index 0a7097a..37885f5 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
@@ -18,7 +18,7 @@
     this.__db = __db
   }
 
-  public override fun baseConcrete(): Unit = performBlocking(__db, false, true) {
+  public override fun baseConcrete(): Unit = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.baseConcrete()
   }
 
@@ -26,11 +26,11 @@
     super@MyDao_Impl.baseSuspendConcrete()
   }
 
-  public override fun concrete(): Unit = performBlocking(__db, false, true) {
+  public override fun concrete(): Unit = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concrete()
   }
 
-  internal override fun concreteInternal(): Unit = performBlocking(__db, false, true) {
+  internal override fun concreteInternal(): Unit = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concreteInternal()
   }
 
@@ -39,7 +39,7 @@
   }
 
   public override fun concreteWithVararg(vararg arr: Long): Unit = performBlocking(__db, false,
-      true) {
+      true) { _ ->
     super@MyDao_Impl.concreteWithVararg(*arr)
   }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
index c4590e2..95deee1 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
@@ -21,7 +21,7 @@
     this.__db = __db
   }
 
-  public override fun baseConcrete(): Unit = performBlocking(__db, false, true) {
+  public override fun baseConcrete(): Unit = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.baseConcrete()
   }
 
@@ -29,21 +29,21 @@
     super@MyDao_Impl.baseSuspendConcrete()
   }
 
-  public override fun concrete(): Unit = performBlocking(__db, false, true) {
+  public override fun concrete(): Unit = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concrete()
   }
 
-  public override fun concreteWithReturn(): String = performBlocking(__db, false, true) {
+  public override fun concreteWithReturn(): String = performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concreteWithReturn()
   }
 
   public override fun concreteWithParamsAndReturn(text: String, num: Long): String =
-      performBlocking(__db, false, true) {
+      performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concreteWithParamsAndReturn(text, num)
   }
 
   public override fun concreteWithFunctionalParam(block: Function0<Unit>): Unit =
-      performBlocking(__db, false, true) {
+      performBlocking(__db, false, true) { _ ->
     super@MyDao_Impl.concreteWithFunctionalParam(block)
   }
 
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 7039636..5634a6c 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -487,10 +487,11 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T, C> T findAndInstantiateDatabaseImpl(Class<C> klass, optional String suffix);
   }
 
-  @RestrictTo({androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class RelationUtil {
-    method public static <K, V> void recursiveFetchArrayMap(androidx.collection.ArrayMap<K,V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super androidx.collection.ArrayMap<K,V>,kotlin.Unit> fetchBlock);
-    method public static <K, V> void recursiveFetchHashMap(java.util.HashMap<K,V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super java.util.HashMap<K,V>,kotlin.Unit> fetchBlock);
-    method public static <V> void recursiveFetchLongSparseArray(androidx.collection.LongSparseArray<V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super androidx.collection.LongSparseArray<V>,kotlin.Unit> fetchBlock);
+  public final class RelationUtil {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <K, V> void recursiveFetchArrayMap(androidx.collection.ArrayMap<K,V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super androidx.collection.ArrayMap<K,V>,kotlin.Unit> fetchBlock);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <K, V> void recursiveFetchHashMap(java.util.HashMap<K,V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super java.util.HashMap<K,V>,kotlin.Unit> fetchBlock);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <V> void recursiveFetchLongSparseArray(androidx.collection.LongSparseArray<V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super androidx.collection.LongSparseArray<V>,kotlin.Unit> fetchBlock);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <K, V> void recursiveFetchMap(java.util.Map<K,V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super java.util.Map<K,V>,kotlin.Unit> fetchBlock);
   }
 
   public final class SQLiteConnectionUtil {
@@ -499,6 +500,7 @@
   }
 
   public final class SQLiteStatementUtil {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getColumnIndex(androidx.sqlite.SQLiteStatement stmt, String name);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getColumnIndexOrThrow(androidx.sqlite.SQLiteStatement stmt, String name);
   }
 
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/RelationUtil.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/RelationUtil.android.kt
index cefa581..e4f7a9f 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/RelationUtil.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/RelationUtil.android.kt
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
+@file:JvmMultifileClass
 @file:JvmName("RelationUtil")
-@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 
 package androidx.room.util
 
@@ -32,6 +32,7 @@
  * @param isRelationCollection - True if [V] is a [Collection] which means it is non null.
  * @param fetchBlock - A lambda for calling the generated _fetchRelationship function.
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 fun <K : Any, V> recursiveFetchHashMap(
     map: HashMap<K, V>,
     isRelationCollection: Boolean,
@@ -73,6 +74,7 @@
 /**
  * Same as [recursiveFetchHashMap] but for [LongSparseArray].
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 fun <V> recursiveFetchLongSparseArray(
     map: LongSparseArray<V>,
     isRelationCollection: Boolean,
@@ -112,6 +114,7 @@
 /**
  * Same as [recursiveFetchHashMap] but for [ArrayMap].
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 fun <K : Any, V> recursiveFetchArrayMap(
     map: ArrayMap<K, V>,
     isRelationCollection: Boolean,
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StatementUtil.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StatementUtil.android.kt
index 6b4d5d2..587b3fd 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StatementUtil.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StatementUtil.android.kt
@@ -27,12 +27,12 @@
  *
  * The implementation also contains Android-specific patches to workaround issues on older devices.
  */
-internal actual fun SQLiteStatement.getColumnIndex(name: String): Int {
-    var index = this.columnIndexOf(name)
+internal actual fun SQLiteStatement.columnIndexOf(name: String): Int {
+    var index = this.columnIndexOfCommon(name)
     if (index >= 0) {
         return index
     }
-    index = this.columnIndexOf("`$name`")
+    index = this.columnIndexOfCommon("`$name`")
     return if (index >= 0) {
         index
     } else {
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/RelationUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/RelationUtil.kt
new file mode 100644
index 0000000..3a11b73
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/RelationUtil.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("RelationUtil")
+
+package androidx.room.util
+
+import androidx.annotation.RestrictTo
+import kotlin.jvm.JvmMultifileClass
+import kotlin.jvm.JvmName
+
+/**
+ * Utility function used in generated code to recursively fetch relationships when the amount of
+ * keys exceed [MAX_BIND_PARAMETER_CNT].
+ *
+ * @param map - The map containing the relationship keys to fill-in.
+ * @param isRelationCollection - True if [V] is a [Collection] which means it is non null.
+ * @param fetchBlock - A lambda for calling the generated _fetchRelationship function.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <K : Any, V> recursiveFetchMap(
+    map: MutableMap<K, V>,
+    isRelationCollection: Boolean,
+    fetchBlock: (MutableMap<K, V>) -> Unit
+) {
+    val tmpMap = mutableMapOf<K, V>()
+    var count = 0
+    for (key in map.keys) {
+        // Safe because `V` is a nullable type arg when isRelationCollection == false and vice versa
+        @Suppress("UNCHECKED_CAST")
+        if (isRelationCollection) {
+            tmpMap[key] = map[key] as V
+        } else {
+            tmpMap[key] = null as V
+        }
+        count++
+        if (count == MAX_BIND_PARAMETER_CNT) {
+            // recursively load that batch
+            fetchBlock(tmpMap)
+            // for non collection relation, put the loaded batch in the original map,
+            // not needed when dealing with collections since references are passed
+            if (!isRelationCollection) {
+                map.putAll(tmpMap)
+            }
+            tmpMap.clear()
+            count = 0
+        }
+    }
+    if (count > 0) {
+        // load the last batch
+        fetchBlock(tmpMap)
+        // for non collection relation, put the last batch in the original map
+        if (!isRelationCollection) {
+            map.putAll(tmpMap)
+        }
+    }
+}
+
+/**
+ * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
+ */
+internal const val MAX_BIND_PARAMETER_CNT = 999
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt
index d4ebdd5..2d68ae8 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt
@@ -69,11 +69,11 @@
 ): Set<TableInfo.ForeignKey> {
     // this seems to return everything in order but it is not documented so better be safe
     connection.prepare("PRAGMA foreign_key_list(`$tableName`)").use { stmt ->
-        val idColumnIndex = stmt.getColumnIndex("id")
-        val seqColumnIndex = stmt.getColumnIndex("seq")
-        val tableColumnIndex = stmt.getColumnIndex("table")
-        val >
-        val >
+        val idColumnIndex = stmt.columnIndexOf("id")
+        val seqColumnIndex = stmt.columnIndexOf("seq")
+        val tableColumnIndex = stmt.columnIndexOf("table")
+        val >
+        val >
         val ordered = readForeignKeyFieldMappings(stmt)
 
         // Reset cursor as readForeignKeyFieldMappings has moved it
@@ -132,10 +132,10 @@
 private fun readForeignKeyFieldMappings(
     stmt: SQLiteStatement
 ): List<ForeignKeyWithSequence> {
-    val idColumnIndex = stmt.getColumnIndex("id")
-    val seqColumnIndex = stmt.getColumnIndex("seq")
-    val fromColumnIndex = stmt.getColumnIndex("from")
-    val toColumnIndex = stmt.getColumnIndex("to")
+    val idColumnIndex = stmt.columnIndexOf("id")
+    val seqColumnIndex = stmt.columnIndexOf("seq")
+    val fromColumnIndex = stmt.columnIndexOf("from")
+    val toColumnIndex = stmt.columnIndexOf("to")
 
     return buildList {
         while (stmt.step()) {
@@ -160,11 +160,11 @@
             return emptyMap()
         }
 
-        val nameIndex = stmt.getColumnIndex("name")
-        val typeIndex = stmt.getColumnIndex("type")
-        val notNullIndex = stmt.getColumnIndex("notnull")
-        val pkIndex = stmt.getColumnIndex("pk")
-        val defaultValueIndex = stmt.getColumnIndex("dflt_value")
+        val nameIndex = stmt.columnIndexOf("name")
+        val typeIndex = stmt.columnIndexOf("type")
+        val notNullIndex = stmt.columnIndexOf("notnull")
+        val pkIndex = stmt.columnIndexOf("pk")
+        val defaultValueIndex = stmt.columnIndexOf("dflt_value")
 
         return buildMap {
             do {
@@ -195,9 +195,9 @@
  */
 private fun readIndices(connection: SQLiteConnection, tableName: String): Set<TableInfo.Index>? {
     connection.prepare("PRAGMA index_list(`$tableName`)").use { stmt ->
-        val nameColumnIndex = stmt.getColumnIndex("name")
-        val originColumnIndex = stmt.getColumnIndex("origin")
-        val uniqueIndex = stmt.getColumnIndex("unique")
+        val nameColumnIndex = stmt.columnIndexOf("name")
+        val originColumnIndex = stmt.columnIndexOf("origin")
+        val uniqueIndex = stmt.columnIndexOf("unique")
         if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
             // we cannot read them so better not validate any index.
             return null
@@ -228,10 +228,10 @@
     unique: Boolean
 ): TableInfo.Index? {
     return connection.prepare("PRAGMA index_xinfo(`$name`)").use { stmt ->
-        val seqnoColumnIndex = stmt.getColumnIndex("seqno")
-        val cidColumnIndex = stmt.getColumnIndex("cid")
-        val nameColumnIndex = stmt.getColumnIndex("name")
-        val descColumnIndex = stmt.getColumnIndex("desc")
+        val seqnoColumnIndex = stmt.columnIndexOf("seqno")
+        val cidColumnIndex = stmt.columnIndexOf("cid")
+        val nameColumnIndex = stmt.columnIndexOf("name")
+        val descColumnIndex = stmt.columnIndexOf("desc")
         if (
             seqnoColumnIndex == -1 ||
             cidColumnIndex == -1 ||
@@ -265,7 +265,7 @@
     return buildSet {
         connection.prepare("PRAGMA table_info(`$tableName`)").use { stmt ->
             if (!stmt.step()) return@use
-            val nameIndex = stmt.getColumnIndex("name")
+            val nameIndex = stmt.columnIndexOf("name")
             do {
                 add(stmt.getText(nameIndex))
             } while (stmt.step())
@@ -278,7 +278,7 @@
         "SELECT * FROM sqlite_master WHERE `name` = '$tableName'"
     ).use { stmt ->
         if (stmt.step()) {
-            stmt.getText(stmt.getColumnIndex("sql"))
+            stmt.getText(stmt.columnIndexOf("sql"))
         } else {
             ""
         }
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
index cebd289..d45b94f 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
@@ -30,7 +30,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 fun getColumnIndexOrThrow(stmt: SQLiteStatement, name: String): Int {
-    val index: Int = stmt.getColumnIndex(name)
+    val index: Int = stmt.columnIndexOf(name)
     if (index >= 0) {
         return index
     }
@@ -43,13 +43,21 @@
 /**
  * Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
  */
-internal expect fun SQLiteStatement.getColumnIndex(name: String): Int
+internal expect fun SQLiteStatement.columnIndexOf(name: String): Int
 
 // TODO(b/322183292): Consider optimizing by creating a String->Int map, similar to Android
-internal fun SQLiteStatement.columnIndexOf(name: String): Int {
+internal fun SQLiteStatement.columnIndexOfCommon(name: String): Int {
     val columnCount = getColumnCount()
     for (i in 0 until columnCount) {
         if (name == getColumnName(i)) return i
     }
     return -1
 }
+
+/**
+ * Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun getColumnIndex(stmt: SQLiteStatement, name: String): Int {
+    return stmt.columnIndexOf(name)
+}
diff --git a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/util/StatementUtil.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/util/StatementUtil.jvmNative.kt
index 437fe5c..96c4bae 100644
--- a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/util/StatementUtil.jvmNative.kt
+++ b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/util/StatementUtil.jvmNative.kt
@@ -26,4 +26,4 @@
 /**
  * Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
  */
-internal actual fun SQLiteStatement.getColumnIndex(name: String): Int = columnIndexOf(name)
+internal actual fun SQLiteStatement.columnIndexOf(name: String): Int = columnIndexOfCommon(name)
diff --git a/settings.gradle b/settings.gradle
index 20624d7..8473061 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -405,6 +405,7 @@
 includeProject(":benchmark:integration-tests:macrobenchmark", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:macrobenchmark-target", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:startup-benchmark", [BuildType.MAIN])
+includeProject(":binarycompatibilityvalidator:binarycompatibilityvalidator", [BuildType.MAIN])
 includeProject(":biometric:biometric", [BuildType.MAIN])
 includeProject(":biometric:biometric-ktx", [BuildType.MAIN])
 includeProject(":biometric:biometric-ktx-samples", "biometric/biometric-ktx/samples", [BuildType.MAIN])
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index 8df8903..1f3d1d3 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -106,6 +106,7 @@
             KonanTarget.ANDROID_X86,
             KonanTarget.MACOS_ARM64,
             KonanTarget.MACOS_X64,
+            KonanTarget.MINGW_X64,
             KonanTarget.LINUX_X64,
     ].collect { it.INSTANCE } // Use INSTANCE to get object class instance from kotlin
 
@@ -144,7 +145,7 @@
     )
 
     // Define C++ compilation of JNI
-    def jvmArtJniImplementation = createNativeCompilation("jvmArtJniImplementation") {
+    def jvmArtJniImplementation = createNativeCompilation("sqliteJni") {
         configureEachTarget { nativeCompilation ->
             // add JNI headers as sources
             nativeCompilation.addJniHeaders()
diff --git a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
index 4b0ec4b..22b981a 100644
--- a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
+++ b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
@@ -33,7 +33,7 @@
 
     private companion object {
         init {
-            NativeLibraryLoader.loadLibrary("jvmArtJniImplementation")
+            NativeLibraryLoader.loadLibrary("sqliteJni")
         }
     }
 }
diff --git a/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt b/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
index c54b9a6..9edb7a3 100644
--- a/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
+++ b/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
@@ -16,8 +16,11 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
-    actual fun loadLibrary(name: String) {
+    actual fun loadLibrary(name: String): Unit = synchronized(this) {
         System.loadLibrary(name)
     }
 }
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
index 6930b5f..5482ca3 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
@@ -16,6 +16,9 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal expect object NativeLibraryLoader {
     /**
      * Loads the given native library via JNI (if running on JVM or Android).
diff --git a/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt b/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
index cae82a8..79eb81a 100644
--- a/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
+++ b/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
@@ -20,16 +20,40 @@
 import java.nio.file.StandardCopyOption
 import java.util.Locale
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
     // TODO(b/304281116): Generate this via Gradle so it is consistent.
-    actual fun loadLibrary(name: String) {
+    actual fun loadLibrary(name: String): Unit = synchronized(this) {
         try {
             System.loadLibrary(name)
             return
-        } catch (error: Throwable) {
-            // looks like we are not on Android, continue
+        } catch (error: UnsatisfiedLinkError) {
+            // Likely not on Android, continue...
         }
-        // TODO(b/304281116): Temporary loading implementation
+        // TODO(b/304281116): Improve loading implementation
+        val libResourceName = getResourceName(name)
+        val libTempCopy = Files.createTempFile("androidx_$name", null)
+            .apply { toFile().deleteOnExit() }
+        NativeLibraryLoader::class.java.classLoader!!.getResourceAsStream(
+            libResourceName
+        ).use { resourceStream ->
+            checkNotNull(resourceStream) {
+                "Cannot find a suitable SQLite binary for ${System.getProperty("os.name")} | " +
+                    "${System.getProperty("os.arch")}. Please file a bug at " +
+                    "https://issuetracker.google.com/issues/new?component=460784"
+            }
+            Files.copy(resourceStream, libTempCopy, StandardCopyOption.REPLACE_EXISTING)
+        }
+        @Suppress("UnsafeDynamicallyLoadedCode")
+        System.load(libTempCopy.toFile().canonicalPath)
+    }
+
+    /**
+     * Gets the JAR's resource file path to the native library.
+     */
+    private fun getResourceName(name: String): String {
         val osName =
             System.getProperty("os.name")?.lowercase(Locale.US) ?: error("Cannot read osName")
         val osArch =
@@ -37,6 +61,7 @@
         val osPrefix = when {
             osName.contains("linux") -> "linux"
             osName.contains("mac") || osName.contains("osx") -> "osx"
+            osName.contains("windows") -> "windows"
             else -> error("Unsupported operating system: $osName")
         }
         val archSuffix = when {
@@ -50,19 +75,12 @@
             else -> error("Unsupported architecture: $osArch")
         }
         val resourceFolder = "${osPrefix}_$archSuffix"
-        val ext = if (osPrefix == "linux") { "so" } else { "dylib" }
-        val resourceName = "$resourceFolder/lib$name.$ext"
-        val nativeLibCopy = Files.createTempFile("androidx_$name", null)
-        nativeLibCopy.toFile().deleteOnExit()
-        NativeLibraryLoader::class.java.classLoader!!.getResourceAsStream(
-            resourceName
-        ).use { resourceStream ->
-            checkNotNull(resourceStream) {
-                "Cannot find resource $resourceName"
-            }
-            Files.copy(resourceStream, nativeLibCopy, StandardCopyOption.REPLACE_EXISTING)
+        val extension = when (osPrefix) {
+            "linux" -> "so"
+            "osx" -> "dylib"
+            "windows" -> "dll"
+            else -> error("Unsupported operating system: $osName")
         }
-        @Suppress("UnsafeDynamicallyLoadedCode")
-        System.load(nativeLibCopy.toFile().canonicalPath)
+        return "natives/$resourceFolder/lib$name.$extension"
     }
 }
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
index 1ae2bf8..c1cf339 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
@@ -16,6 +16,9 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
     actual fun loadLibrary(name: String) {
         // no-op, we are already in native code
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
index f4a1f50..d21461e 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/ComposeTest.java
@@ -43,7 +43,7 @@
         UiObject2 column = mDevice.findObject(By.scrollable(true));
         assertNotNull("Scrollable container not found", column);
         UiObject2 button = column.scrollUntil(Direction.DOWN,
-                Until.findObject(By.clazz(Button.class)));
+                Until.findObject(By.clazz(Button.class).hasParent(By.hasChild(By.text("Update")))));
         assertNotNull("Button not found after scrolling", button);
 
         // Click and wait for change.
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
index bff0f577..37a9d00 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
@@ -35,7 +35,6 @@
     private static final BySelector STATUS_BAR = By.res("com.android.systemui", "status_bar");
 
     @Test
-    @SdkSuppress(minSdkVersion = 21)
     public void testMultiWindow_statusBar() {
         // Can locate objects outside of current context.
         assertTrue(mDevice.hasObject(STATUS_BAR));
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 0ecbc8d..4192492 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -278,7 +278,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 21) // Quick settings menu might not be present prior to API 21.
     public void testOpenQuickSettings() {
         mDevice.openQuickSettings();
 
diff --git a/test/uiautomator/uiautomator/api/current.txt b/test/uiautomator/uiautomator/api/current.txt
index 89cf12f6..3bfcf3f 100644
--- a/test/uiautomator/uiautomator/api/current.txt
+++ b/test/uiautomator/uiautomator/api/current.txt
@@ -169,7 +169,7 @@
     method public void dumpWindowHierarchy(java.io.File) throws java.io.IOException;
     method public void dumpWindowHierarchy(java.io.OutputStream) throws java.io.IOException;
     method @Deprecated public void dumpWindowHierarchy(String);
-    method @Discouraged(message="Can be useful for simple commands, but lacks support for proper error handling, input data, or complex commands (quotes, pipes) that can be obtained from UiAutomation#executeShellCommandRwe or similar utilities.") @RequiresApi(21) public String executeShellCommand(String) throws java.io.IOException;
+    method @Discouraged(message="Can be useful for simple commands, but lacks support for proper error handling, input data, or complex commands (quotes, pipes) that can be obtained from UiAutomation#executeShellCommandRwe or similar utilities.") public String executeShellCommand(String) throws java.io.IOException;
     method public androidx.test.uiautomator.UiObject2! findObject(androidx.test.uiautomator.BySelector);
     method public androidx.test.uiautomator.UiObject findObject(androidx.test.uiautomator.UiSelector);
     method public java.util.List<androidx.test.uiautomator.UiObject2!> findObjects(androidx.test.uiautomator.BySelector);
diff --git a/test/uiautomator/uiautomator/api/restricted_current.txt b/test/uiautomator/uiautomator/api/restricted_current.txt
index 89cf12f6..3bfcf3f 100644
--- a/test/uiautomator/uiautomator/api/restricted_current.txt
+++ b/test/uiautomator/uiautomator/api/restricted_current.txt
@@ -169,7 +169,7 @@
     method public void dumpWindowHierarchy(java.io.File) throws java.io.IOException;
     method public void dumpWindowHierarchy(java.io.OutputStream) throws java.io.IOException;
     method @Deprecated public void dumpWindowHierarchy(String);
-    method @Discouraged(message="Can be useful for simple commands, but lacks support for proper error handling, input data, or complex commands (quotes, pipes) that can be obtained from UiAutomation#executeShellCommandRwe or similar utilities.") @RequiresApi(21) public String executeShellCommand(String) throws java.io.IOException;
+    method @Discouraged(message="Can be useful for simple commands, but lacks support for proper error handling, input data, or complex commands (quotes, pipes) that can be obtained from UiAutomation#executeShellCommandRwe or similar utilities.") public String executeShellCommand(String) throws java.io.IOException;
     method public androidx.test.uiautomator.UiObject2! findObject(androidx.test.uiautomator.BySelector);
     method public androidx.test.uiautomator.UiObject findObject(androidx.test.uiautomator.UiSelector);
     method public java.util.List<androidx.test.uiautomator.UiObject2!> findObjects(androidx.test.uiautomator.BySelector);
diff --git a/test/uiautomator/uiautomator/lint-baseline.xml b/test/uiautomator/uiautomator/lint-baseline.xml
index 8e8e058..eb168df 100644
--- a/test/uiautomator/uiautomator/lint-baseline.xml
+++ b/test/uiautomator/uiautomator/lint-baseline.xml
@@ -11,141 +11,6 @@
     </issue>
 
     <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/GestureController.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/GestureController.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="            boolean supportsWakeButton = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH;"
-        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/InteractionController.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="            boolean supportsSleepButton = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH;"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/InteractionController.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiObject.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiObject.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiObject2.java"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 21"
-        errorLine1="    @RequiresApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/test/uiautomator/UiObject2.java"/>
-    </issue>
-
-    <issue
         id="LambdaLast"
         message="Functional interface parameters (such as parameter 1, &quot;condition&quot;, in androidx.test.uiautomator.UiDevice.wait) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
         errorLine1="    public &lt;U> U wait(@NonNull Condition&lt;? super UiDevice, U> condition, long timeout) {"
diff --git a/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java b/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
index 0286b1b..172a19f 100644
--- a/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
+++ b/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
@@ -88,7 +88,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     public void testGetDisplayMetrics() throws IOException {
         String densityCmdOutput = mDevice.executeShellCommand("wm density");
         Pattern densityPattern = Pattern.compile("^Physical\\sdensity:\\s(\\d+)\\D+.*");
@@ -202,7 +201,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     public void testExecuteShellCommand() throws IOException {
         String output = mDevice.executeShellCommand("pm list packages");
         assertTrue(output.contains("package:androidx.test.uiautomator.test"));
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
index 8e3523a..ce4fd2f 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
@@ -17,14 +17,10 @@
 package androidx.test.uiautomator;
 
 import android.graphics.Rect;
-import android.os.Build;
 import android.util.Log;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
-import androidx.annotation.DoNotInline;
-import androidx.annotation.RequiresApi;
-
 /**
  * This class contains static helper methods to work with
  * {@link AccessibilityNodeInfo}
@@ -68,15 +64,12 @@
         }
         intersectOrWarn(nodeRect, displayRect);
 
-        // On platforms that give us access to the node's window
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            // Trim any portion of the bounds that are outside the window
-            Rect bounds = new Rect();
-            AccessibilityWindowInfo window = Api21Impl.getWindow(node);
-            if (window != null) {
-                Api21Impl.getBoundsInScreen(window, bounds);
-                intersectOrWarn(nodeRect, bounds);
-            }
+        // Trim any portion of the bounds that are outside the window
+        Rect bounds = new Rect();
+        AccessibilityWindowInfo window = node.getWindow();
+        if (window != null) {
+            window.getBoundsInScreen(bounds);
+            intersectOrWarn(nodeRect, bounds);
         }
 
         // Trim the bounds into any scrollable ancestor, if required.
@@ -106,21 +99,4 @@
             Log.v(TAG, String.format("No overlap between %s and %s. Ignoring.", target, bounds));
         }
     }
-
-    @RequiresApi(21)
-    static class Api21Impl {
-        private Api21Impl() {
-        }
-
-        @DoNotInline
-        static void getBoundsInScreen(AccessibilityWindowInfo accessibilityWindowInfo,
-                Rect outBounds) {
-            accessibilityWindowInfo.getBoundsInScreen(outBounds);
-        }
-
-        @DoNotInline
-        static AccessibilityWindowInfo getWindow(AccessibilityNodeInfo accessibilityNodeInfo) {
-            return accessibilityNodeInfo.getWindow();
-        }
-    }
 }
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/GestureController.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/GestureController.java
index 6366d96..6f9863c 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/GestureController.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/GestureController.java
@@ -25,9 +25,7 @@
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -136,15 +134,9 @@
         try {
             int displayId = pending.peek().displayId();
             Display display = mDevice.getDisplayById(displayId);
-            float displayRefreshRate;
-            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
-                float[] refreshRates = Api21Impl.getSupportedRefreshRates(display);
-                Arrays.sort(refreshRates);
-                displayRefreshRate = refreshRates[refreshRates.length - 1];
-            } else {
-                // Set to current refresh rate if API version is lower than 21.
-                displayRefreshRate = display.getRefreshRate();
-            }
+            float[] refreshRates = display.getSupportedRefreshRates();
+            Arrays.sort(refreshRates);
+            float displayRefreshRate = refreshRates[refreshRates.length - 1];
             injectionDelay = (long) (500 / displayRefreshRate);
         } catch (Exception e) {
             Log.e(TAG, "Fail to update motion event delay", e);
@@ -305,15 +297,4 @@
     UiDevice getDevice() {
         return mDevice;
     }
-
-    @RequiresApi(21)
-    private static class Api21Impl {
-        private Api21Impl() {
-        }
-
-        @DoNotInline
-        static float[] getSupportedRefreshRates(Display display) {
-            return display.getSupportedRefreshRates();
-        }
-    }
 }
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
index 58d2d0f..efb05c4 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
@@ -20,7 +20,6 @@
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
 import android.graphics.Point;
-import android.os.Build;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -457,8 +456,7 @@
      */
     public boolean wakeDevice() throws RemoteException {
         if(!isScreenOn()) {
-            boolean supportsWakeButton = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH;
-            sendKey(supportsWakeButton ? KeyEvent.KEYCODE_WAKEUP : KeyEvent.KEYCODE_POWER, 0);
+            sendKey(KeyEvent.KEYCODE_WAKEUP, 0);
             return true;
         }
         return false;
@@ -473,8 +471,7 @@
      */
     public boolean sleepDevice() throws RemoteException {
         if(isScreenOn()) {
-            boolean supportsSleepButton = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH;
-            sendKey(supportsSleepButton ? KeyEvent.KEYCODE_SLEEP : KeyEvent.KEYCODE_POWER, 0);
+            sendKey(KeyEvent.KEYCODE_SLEEP , 0);
             return true;
         }
         return false;
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
index a70fb2e..0503e6c 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
@@ -1351,11 +1351,10 @@
     @Discouraged(message = "Can be useful for simple commands, but lacks support for proper error"
             + " handling, input data, or complex commands (quotes, pipes) that can be obtained "
             + "from UiAutomation#executeShellCommandRwe or similar utilities.")
-    @RequiresApi(21)
     @NonNull
     public String executeShellCommand(@NonNull String cmd) throws IOException {
         Log.d(TAG, String.format("Executing shell command: %s", cmd));
-        try (ParcelFileDescriptor pfd = Api21Impl.executeShellCommand(getUiAutomation(), cmd);
+        try (ParcelFileDescriptor pfd = getUiAutomation().executeShellCommand(cmd);
              FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
             byte[] buf = new byte[512];
             int bytesRead;
@@ -1394,7 +1393,6 @@
         return p;
     }
 
-    @RequiresApi(21)
     private List<AccessibilityWindowInfo> getWindows(UiAutomation uiAutomation) {
         // Support multi-display searches for API level 30 and up.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -1406,7 +1404,7 @@
             }
             return windowList;
         }
-        return Api21Impl.getWindows(uiAutomation);
+        return uiAutomation.getWindows();
     }
 
     /** Returns a list containing the root {@link AccessibilityNodeInfo}s for each active window */
@@ -1423,16 +1421,14 @@
         } else {
             Log.w(TAG, "Active window root not found.");
         }
-        // Support multi-window searches for API level 21 and up.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            for (final AccessibilityWindowInfo window : getWindows(uiAutomation)) {
-                final AccessibilityNodeInfo root = Api21Impl.getRoot(window);
-                if (root == null) {
-                    Log.w(TAG, "Skipping null root node for window: " + window);
-                    continue;
-                }
-                roots.add(root);
+        // Add all windows to support multi-window/display searches.
+        for (final AccessibilityWindowInfo window : getWindows(uiAutomation)) {
+            final AccessibilityNodeInfo root = window.getRoot();
+            if (root == null) {
+                Log.w(TAG, "Skipping null root node for window: " + window);
+                continue;
             }
+            roots.add(root);
         }
         return roots.toArray(new AccessibilityNodeInfo[0]);
     }
@@ -1489,10 +1485,8 @@
         boolean serviceFlagsChanged = serviceInfo.flags != mCachedServiceFlags;
         if (serviceFlagsChanged
                 || SystemClock.uptimeMillis() - mLastServiceFlagsTime > SERVICE_FLAGS_TIMEOUT) {
-            // Enable multi-window support for API 21+.
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-            }
+            // Enable multi-window support.
+            serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
             // Enable or disable hierarchy compression.
             if (mCompressed) {
                 serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
@@ -1520,27 +1514,6 @@
         return mInteractionController;
     }
 
-    @RequiresApi(21)
-    static class Api21Impl {
-        private Api21Impl() {
-        }
-
-        @DoNotInline
-        static ParcelFileDescriptor executeShellCommand(UiAutomation uiAutomation, String command) {
-            return uiAutomation.executeShellCommand(command);
-        }
-
-        @DoNotInline
-        static List<AccessibilityWindowInfo> getWindows(UiAutomation uiAutomation) {
-            return uiAutomation.getWindows();
-        }
-
-        @DoNotInline
-        static AccessibilityNodeInfo getRoot(AccessibilityWindowInfo accessibilityWindowInfo) {
-            return accessibilityWindowInfo.getRoot();
-        }
-    }
-
     @RequiresApi(24)
     static class Api24Impl {
         private Api24Impl() {
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
index 6be203f..d42a8f2 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
@@ -18,11 +18,9 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
-import android.view.KeyEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -581,22 +579,14 @@
         if (text == null) {
             text = "";
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            // ACTION_SET_TEXT is added in API 21.
-            AccessibilityNodeInfo node = findAccessibilityNodeInfo(
-                    mConfig.getWaitForSelectorTimeout());
-            if (node == null) {
-                throw new UiObjectNotFoundException(getSelector().toString());
-            }
-            Log.d(TAG, String.format("Setting text to '%s'.", text));
-            Bundle args = new Bundle();
-            args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
-            return node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
-        } else {
-            clearTextField();
-            Log.d(TAG, String.format("Setting text to '%s'.", text));
-            return getInteractionController().sendText(text);
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if (node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
         }
+        Log.d(TAG, String.format("Setting text to '%s'.", text));
+        Bundle args = new Bundle();
+        args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
+        return node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
     }
 
     /**
@@ -618,26 +608,7 @@
         CharSequence text = node.getText();
         // do nothing if already empty
         if (text != null && text.length() > 0) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                setText("");
-            } else {
-                Log.d(TAG, "Setting text to ''.");
-                Bundle selectionArgs = new Bundle();
-                // select all of the existing text
-                selectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
-                selectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
-                        text.length());
-                boolean ret = node.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
-                if (!ret) {
-                    Log.w(TAG, "ACTION_FOCUS on text field failed.");
-                }
-                ret = node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, selectionArgs);
-                if (!ret) {
-                    Log.w(TAG, "ACTION_SET_SELECTION on text field failed.");
-                }
-                // now delete all
-                getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
-            }
+            setText("");
         }
     }
 
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 1fa068b..2da3b2a 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -25,7 +25,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
@@ -97,7 +96,7 @@
         // Fetch and cache display information. This is safe as moving the underlying view to
         // another display would invalidate the cached node and require recreating this UiObject2.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            AccessibilityWindowInfo window = Api21Impl.getWindow(cachedNode);
+            AccessibilityWindowInfo window = cachedNode.getWindow();
             mDisplayId = window == null ? Display.DEFAULT_DISPLAY : Api30Impl.getDisplayId(window);
         } else {
             mDisplayId = Display.DEFAULT_DISPLAY;
@@ -969,37 +968,10 @@
         }
 
         Log.d(TAG, String.format("Setting text to '%s'.", text));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            // ACTION_SET_TEXT is added in API 21.
-            Bundle args = new Bundle();
-            args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
-            if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)) {
-                // TODO: Decide if we should throw here
-                Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_TEXT) failed");
-            }
-        } else {
-            CharSequence currentText = node.getText();
-            if (currentText == null || !text.contentEquals(currentText)) {
-                // Give focus to the object. Expect this to fail if the object already has focus.
-                if (!node.performAction(AccessibilityNodeInfo.ACTION_FOCUS) && !node.isFocused()) {
-                    // TODO: Decide if we should throw here
-                    Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_FOCUS) failed");
-                }
-                // Select the existing text. Expect this to fail if there is no existing text.
-                Bundle args = new Bundle();
-                args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
-                args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
-                        currentText == null ? 0 : currentText.length());
-                if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, args) &&
-                        currentText != null && currentText.length() > 0) {
-                    // TODO: Decide if we should throw here
-                    Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_SELECTION) failed");
-                }
-                // Send the delete key to clear the existing text, then send the new text
-                InteractionController ic = getDevice().getInteractionController();
-                ic.sendKey(KeyEvent.KEYCODE_DEL, 0);
-                ic.sendText(text);
-            }
+        Bundle args = new Bundle();
+        args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
+        if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)) {
+            Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_TEXT) failed");
         }
     }
 
@@ -1055,23 +1027,6 @@
         return mDevice;
     }
 
-    @RequiresApi(21)
-    static class Api21Impl {
-        private Api21Impl() {
-        }
-
-        @DoNotInline
-        static AccessibilityWindowInfo getWindow(AccessibilityNodeInfo accessibilityNodeInfo) {
-            return accessibilityNodeInfo.getWindow();
-        }
-
-        @DoNotInline
-        static void getBoundsInScreen(AccessibilityWindowInfo accessibilityWindowInfo,
-                Rect outBounds) {
-            accessibilityWindowInfo.getBoundsInScreen(outBounds);
-        }
-    }
-
     @RequiresApi(24)
     static class Api24Impl {
         private Api24Impl() {
diff --git a/tv/tv-material/api/current.ignore b/tv/tv-material/api/current.ignore
deleted file mode 100644
index 3bdd09b..0000000
--- a/tv/tv-material/api/current.ignore
+++ /dev/null
@@ -1,57 +0,0 @@
-// Baseline format: 1.0
-AddedClass: androidx.tv.material3.CardContainerColors:
-    Added class androidx.tv.material3.CardContainerColors
-AddedClass: androidx.tv.material3.CardContainerDefaults:
-    Added class androidx.tv.material3.CardContainerDefaults
-AddedClass: androidx.tv.material3.CardContainerKt:
-    Added class androidx.tv.material3.CardContainerKt
-AddedClass: androidx.tv.material3.SurfaceColors:
-    Added class androidx.tv.material3.SurfaceColors
-AddedClass: androidx.tv.material3.SurfaceDefaults:
-    Added class androidx.tv.material3.SurfaceDefaults
-
-
-AddedMethod: androidx.tv.material3.CardDefaults#getScrimBrush():
-    Added method androidx.tv.material3.CardDefaults.getScrimBrush()
-AddedMethod: androidx.tv.material3.ListItemDefaults#getTonalElevation():
-    Added method androidx.tv.material3.ListItemDefaults.getTonalElevation()
-AddedMethod: androidx.tv.material3.ListItemKt#DenseListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource):
-    Added method androidx.tv.material3.ListItemKt.DenseListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource)
-AddedMethod: androidx.tv.material3.ListItemKt#ListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource):
-    Added method androidx.tv.material3.ListItemKt.ListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource)
-AddedMethod: androidx.tv.material3.SurfaceKt#Surface(androidx.compose.ui.Modifier, float, androidx.compose.ui.graphics.Shape, androidx.tv.material3.SurfaceColors, androidx.tv.material3.Border, androidx.tv.material3.Glow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>):
-    Added method androidx.tv.material3.SurfaceKt.Surface(androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,androidx.tv.material3.SurfaceColors,androidx.tv.material3.Border,androidx.tv.material3.Glow,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
-
-
-RemovedClass: androidx.tv.material3.CardLayoutColors:
-    Removed class androidx.tv.material3.CardLayoutColors
-RemovedClass: androidx.tv.material3.CardLayoutDefaults:
-    Removed class androidx.tv.material3.CardLayoutDefaults
-RemovedClass: androidx.tv.material3.CardLayoutKt:
-    Removed class androidx.tv.material3.CardLayoutKt
-RemovedClass: androidx.tv.material3.NonInteractiveSurfaceColors:
-    Removed class androidx.tv.material3.NonInteractiveSurfaceColors
-RemovedClass: androidx.tv.material3.NonInteractiveSurfaceDefaults:
-    Removed class androidx.tv.material3.NonInteractiveSurfaceDefaults
-
-
-RemovedField: androidx.tv.material3.ListItemDefaults#SelectedContinerColorOpacity:
-    Removed field androidx.tv.material3.ListItemDefaults.SelectedContinerColorOpacity
-
-
-RemovedMethod: androidx.tv.material3.CardDefaults#getContainerGradient():
-    Removed method androidx.tv.material3.CardDefaults.getContainerGradient()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getFocusedDisabledBorder():
-    Removed method androidx.tv.material3.ListItemDefaults.getFocusedDisabledBorder()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getListItemElevation():
-    Removed method androidx.tv.material3.ListItemDefaults.getListItemElevation()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getListItemShape():
-    Removed method androidx.tv.material3.ListItemDefaults.getListItemShape()
-RemovedMethod: androidx.tv.material3.ListItemKt#DenseListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource, kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.tv.material3.ListItemKt.DenseListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.jvm.functions.Function0<kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.ListItemKt#ListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource, kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.tv.material3.ListItemKt.ListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.jvm.functions.Function0<kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.SurfaceKt#Surface(androidx.compose.ui.Modifier, float, androidx.compose.ui.graphics.Shape, androidx.tv.material3.NonInteractiveSurfaceColors, androidx.tv.material3.Border, androidx.tv.material3.Glow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>):
-    Removed method androidx.tv.material3.SurfaceKt.Surface(androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,androidx.tv.material3.NonInteractiveSurfaceColors,androidx.tv.material3.Border,androidx.tv.material3.Glow,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.SurfaceKt#getLocalAbsoluteTonalElevation():
-    Removed method androidx.tv.material3.SurfaceKt.getLocalAbsoluteTonalElevation()
diff --git a/tv/tv-material/api/restricted_current.ignore b/tv/tv-material/api/restricted_current.ignore
deleted file mode 100644
index 3bdd09b..0000000
--- a/tv/tv-material/api/restricted_current.ignore
+++ /dev/null
@@ -1,57 +0,0 @@
-// Baseline format: 1.0
-AddedClass: androidx.tv.material3.CardContainerColors:
-    Added class androidx.tv.material3.CardContainerColors
-AddedClass: androidx.tv.material3.CardContainerDefaults:
-    Added class androidx.tv.material3.CardContainerDefaults
-AddedClass: androidx.tv.material3.CardContainerKt:
-    Added class androidx.tv.material3.CardContainerKt
-AddedClass: androidx.tv.material3.SurfaceColors:
-    Added class androidx.tv.material3.SurfaceColors
-AddedClass: androidx.tv.material3.SurfaceDefaults:
-    Added class androidx.tv.material3.SurfaceDefaults
-
-
-AddedMethod: androidx.tv.material3.CardDefaults#getScrimBrush():
-    Added method androidx.tv.material3.CardDefaults.getScrimBrush()
-AddedMethod: androidx.tv.material3.ListItemDefaults#getTonalElevation():
-    Added method androidx.tv.material3.ListItemDefaults.getTonalElevation()
-AddedMethod: androidx.tv.material3.ListItemKt#DenseListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource):
-    Added method androidx.tv.material3.ListItemKt.DenseListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource)
-AddedMethod: androidx.tv.material3.ListItemKt#ListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource):
-    Added method androidx.tv.material3.ListItemKt.ListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource)
-AddedMethod: androidx.tv.material3.SurfaceKt#Surface(androidx.compose.ui.Modifier, float, androidx.compose.ui.graphics.Shape, androidx.tv.material3.SurfaceColors, androidx.tv.material3.Border, androidx.tv.material3.Glow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>):
-    Added method androidx.tv.material3.SurfaceKt.Surface(androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,androidx.tv.material3.SurfaceColors,androidx.tv.material3.Border,androidx.tv.material3.Glow,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
-
-
-RemovedClass: androidx.tv.material3.CardLayoutColors:
-    Removed class androidx.tv.material3.CardLayoutColors
-RemovedClass: androidx.tv.material3.CardLayoutDefaults:
-    Removed class androidx.tv.material3.CardLayoutDefaults
-RemovedClass: androidx.tv.material3.CardLayoutKt:
-    Removed class androidx.tv.material3.CardLayoutKt
-RemovedClass: androidx.tv.material3.NonInteractiveSurfaceColors:
-    Removed class androidx.tv.material3.NonInteractiveSurfaceColors
-RemovedClass: androidx.tv.material3.NonInteractiveSurfaceDefaults:
-    Removed class androidx.tv.material3.NonInteractiveSurfaceDefaults
-
-
-RemovedField: androidx.tv.material3.ListItemDefaults#SelectedContinerColorOpacity:
-    Removed field androidx.tv.material3.ListItemDefaults.SelectedContinerColorOpacity
-
-
-RemovedMethod: androidx.tv.material3.CardDefaults#getContainerGradient():
-    Removed method androidx.tv.material3.CardDefaults.getContainerGradient()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getFocusedDisabledBorder():
-    Removed method androidx.tv.material3.ListItemDefaults.getFocusedDisabledBorder()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getListItemElevation():
-    Removed method androidx.tv.material3.ListItemDefaults.getListItemElevation()
-RemovedMethod: androidx.tv.material3.ListItemDefaults#getListItemShape():
-    Removed method androidx.tv.material3.ListItemDefaults.getListItemShape()
-RemovedMethod: androidx.tv.material3.ListItemKt#DenseListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource, kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.tv.material3.ListItemKt.DenseListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.jvm.functions.Function0<kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.ListItemKt#ListItem(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>, kotlin.jvm.functions.Function0<kotlin.Unit>, float, androidx.tv.material3.ListItemShape, androidx.tv.material3.ListItemColors, androidx.tv.material3.ListItemScale, androidx.tv.material3.ListItemBorder, androidx.tv.material3.ListItemGlow, androidx.compose.foundation.interaction.MutableInteractionSource, kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.tv.material3.ListItemKt.ListItem(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.compose.ui.Modifier,boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>,kotlin.jvm.functions.Function0<kotlin.Unit>,float,androidx.tv.material3.ListItemShape,androidx.tv.material3.ListItemColors,androidx.tv.material3.ListItemScale,androidx.tv.material3.ListItemBorder,androidx.tv.material3.ListItemGlow,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.jvm.functions.Function0<kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.SurfaceKt#Surface(androidx.compose.ui.Modifier, float, androidx.compose.ui.graphics.Shape, androidx.tv.material3.NonInteractiveSurfaceColors, androidx.tv.material3.Border, androidx.tv.material3.Glow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>):
-    Removed method androidx.tv.material3.SurfaceKt.Surface(androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,androidx.tv.material3.NonInteractiveSurfaceColors,androidx.tv.material3.Border,androidx.tv.material3.Glow,kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
-RemovedMethod: androidx.tv.material3.SurfaceKt#getLocalAbsoluteTonalElevation():
-    Removed method androidx.tv.material3.SurfaceKt.getLocalAbsoluteTonalElevation()
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index 740d96f..e443ad2 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -33,8 +33,11 @@
 dependencies {
     api(libs.kotlinStdlib)
 
-    def composeVersion = "1.6.4"
-    api("androidx.annotation:annotation:$composeVersion")
+    def annotationVersion = "1.7.1"
+    def composeVersion = "1.6.5"
+    def profileInstallerVersion = "1.3.1"
+
+    api("androidx.annotation:annotation:$annotationVersion")
     api("androidx.compose.animation:animation:$composeVersion")
     api("androidx.compose.foundation:foundation:$composeVersion")
     api("androidx.compose.foundation:foundation-layout:$composeVersion")
@@ -45,7 +48,7 @@
     api("androidx.compose.ui:ui-graphics:$composeVersion")
     api("androidx.compose.ui:ui-text:$composeVersion")
 
-    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
+    implementation("androidx.profileinstaller:profileinstaller:$profileInstallerVersion")
 
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":compose:runtime:runtime"))
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt
index e3cd485..72e6f56 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryScrollable.kt
@@ -69,6 +69,16 @@
  * LazyList and others. [ScalingLazyColumn] has a build-in rotary support, and accepts
  * [RotaryScrollableBehavior] directly as a parameter.
  *
+ * This modifier handles rotary input devices, used for scrolling. These devices can be categorized
+ * as high-resolution or low-resolution based on their precision.
+ *
+ *  - High-res devices: Offer finer control and can detect smaller rotations.
+ *    This allows for more precise adjustments during scrolling. One example of a high-res
+ *    device is the crown (also known as rotating side button), located on the side of the watch.
+ *  - Low-res devices: Have less granular control, registering larger rotations
+ *    at a time. Scrolling behavior is adapted to compensate for these larger jumps. Examples
+ *    include physical or virtual bezels, positioned around the screen.
+ *
  * This modifier supports rotary scrolling and snapping.
  * The behaviour is configured by the provided [RotaryScrollableBehavior]:
  * either provide [RotaryScrollableDefaults.behavior] for scrolling with/without fling
@@ -103,7 +113,8 @@
 /**
  * An interface for handling scroll events. Has implementations for handling scroll
  * with/without fling [FlingRotaryScrollableBehavior] and for handling snap
- * [LowResSnapRotaryScrollableBehavior], [HighResSnapRotaryScrollableBehavior].
+ * [LowResSnapRotaryScrollableBehavior], [HighResSnapRotaryScrollableBehavior] (see
+ * [Modifier.rotaryScrollable] for descriptions of low-res and high-res devices).
  */
 interface RotaryScrollableBehavior {
 
@@ -321,7 +332,8 @@
  * Handles scroll with fling.
  *
  * @return A scroll with fling implementation of [RotaryScrollableBehavior] which is suitable
- * for both low-res and high-res inputs.
+ * for both low-res and high-res inputs (see [Modifier.rotaryScrollable] for descriptions
+ * of low-res and high-res devices).
  *
  * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
  * @param flingBehavior Logic describing Fling behavior. If null - fling will not happen
@@ -360,7 +372,8 @@
  * Handles scroll with snap.
  *
  * @return A snap implementation of [RotaryScrollableBehavior] which is either suitable for low-res
- * or high-res input.
+ * or high-res input (see [Modifier.rotaryScrollable] for descriptions of low-res
+ * and high-res devices).
  *
  * @param layoutInfoProvider Implementation of [RotarySnapLayoutInfoProvider], which connects
  * scrollableState to a rotary input for snapping scroll actions.
@@ -419,8 +432,9 @@
 
 /**
  * An abstract base class for handling scroll events. Has implementations for handling scroll
- * with/without fling [FlingRotaryScrollableBehavior] and for handling snap [LowResSnapRotaryScrollableBehavior],
- * [HighResSnapRotaryScrollableBehavior].
+ * with/without fling [FlingRotaryScrollableBehavior] and for handling snap
+ * [LowResSnapRotaryScrollableBehavior], [HighResSnapRotaryScrollableBehavior] (see
+ * [Modifier.rotaryScrollable] for descriptions of low-res and high-res devices ).
  */
 internal abstract class BaseRotaryScrollableBehavior : RotaryScrollableBehavior {
 
@@ -797,7 +811,8 @@
  *
  * For a high-res input it has a filtering for events which are coming
  * with an opposite sign (this might happen to devices with rsb,
- * especially at the end of the scroll )
+ * especially at the end of the scroll ) - see [Modifier.rotaryScrollable] for descriptions
+ * of low-res and high-res devices.
  *
  * This scroll behavior supports fling. It can be set with [RotaryFlingHandler].
  */
@@ -873,7 +888,8 @@
 }
 
 /**
- * A scroll behavior for RSB(high-res) input with snapping and without fling.
+ * A scroll behavior for RSB(high-res) input with snapping and without fling (see
+ * [Modifier.rotaryScrollable] for descriptions of low-res and high-res devices ).
  *
  * Threshold for snapping is set dynamically in ThresholdBehavior, which depends
  * on the scroll speed and the average size of the items.
@@ -1030,7 +1046,8 @@
 }
 
 /**
- * A scroll behavior for Bezel(low-res) input with snapping and without fling
+ * A scroll behavior for Bezel(low-res) input with snapping and without fling (see
+ * [Modifier.rotaryScrollable] for descriptions of low-res and high-res devices ).
  *
  * This scroll behavior doesn't support fling.
  */
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index eb3d98e..eaa6197 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -136,12 +136,14 @@
   }
 
   @androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class ColorScheme {
-    ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
-    method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
+    ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
+    method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
     method public long getBackground();
     method public long getError();
+    method public long getErrorContainer();
     method public long getOnBackground();
     method public long getOnError();
+    method public long getOnErrorContainer();
     method public long getOnPrimary();
     method public long getOnPrimaryContainer();
     method public long getOnSecondary();
@@ -158,16 +160,18 @@
     method public long getSecondary();
     method public long getSecondaryContainer();
     method public long getSecondaryDim();
-    method public long getSurface();
-    method public long getSurfaceBright();
-    method public long getSurfaceDim();
+    method public long getSurfaceContainer();
+    method public long getSurfaceContainerHigh();
+    method public long getSurfaceContainerLow();
     method public long getTertiary();
     method public long getTertiaryContainer();
     method public long getTertiaryDim();
     property public final long background;
     property public final long error;
+    property public final long errorContainer;
     property public final long onBackground;
     property public final long onError;
+    property public final long onErrorContainer;
     property public final long onPrimary;
     property public final long onPrimaryContainer;
     property public final long onSecondary;
@@ -184,9 +188,9 @@
     property public final long secondary;
     property public final long secondaryContainer;
     property public final long secondaryDim;
-    property public final long surface;
-    property public final long surfaceBright;
-    property public final long surfaceDim;
+    property public final long surfaceContainer;
+    property public final long surfaceContainerHigh;
+    property public final long surfaceContainerLow;
     property public final long tertiary;
     property public final long tertiaryContainer;
     property public final long tertiaryDim;
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index eb3d98e..eaa6197 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -136,12 +136,14 @@
   }
 
   @androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class ColorScheme {
-    ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
-    method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceDim, optional long surface, optional long surfaceBright, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError);
+    ctor public ColorScheme(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
+    method public androidx.wear.compose.material3.ColorScheme copy(optional long primary, optional long primaryDim, optional long primaryContainer, optional long onPrimary, optional long onPrimaryContainer, optional long secondary, optional long secondaryDim, optional long secondaryContainer, optional long onSecondary, optional long onSecondaryContainer, optional long tertiary, optional long tertiaryDim, optional long tertiaryContainer, optional long onTertiary, optional long onTertiaryContainer, optional long surfaceContainerLow, optional long surfaceContainer, optional long surfaceContainerHigh, optional long onSurface, optional long onSurfaceVariant, optional long outline, optional long outlineVariant, optional long background, optional long onBackground, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer);
     method public long getBackground();
     method public long getError();
+    method public long getErrorContainer();
     method public long getOnBackground();
     method public long getOnError();
+    method public long getOnErrorContainer();
     method public long getOnPrimary();
     method public long getOnPrimaryContainer();
     method public long getOnSecondary();
@@ -158,16 +160,18 @@
     method public long getSecondary();
     method public long getSecondaryContainer();
     method public long getSecondaryDim();
-    method public long getSurface();
-    method public long getSurfaceBright();
-    method public long getSurfaceDim();
+    method public long getSurfaceContainer();
+    method public long getSurfaceContainerHigh();
+    method public long getSurfaceContainerLow();
     method public long getTertiary();
     method public long getTertiaryContainer();
     method public long getTertiaryDim();
     property public final long background;
     property public final long error;
+    property public final long errorContainer;
     property public final long onBackground;
     property public final long onError;
+    property public final long onErrorContainer;
     property public final long onPrimary;
     property public final long onPrimaryContainer;
     property public final long onSecondary;
@@ -184,9 +188,9 @@
     property public final long secondary;
     property public final long secondaryContainer;
     property public final long secondaryDim;
-    property public final long surface;
-    property public final long surfaceBright;
-    property public final long surfaceDim;
+    property public final long surfaceContainer;
+    property public final long surfaceContainerHigh;
+    property public final long surfaceContainerLow;
     property public final long tertiary;
     property public final long tertiaryContainer;
     property public final long tertiaryDim;
diff --git a/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
index cdbce14..2e6efe4 100644
--- a/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
+++ b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/ColorSchemeBenchmark.kt
@@ -135,10 +135,14 @@
                 )
 
                 // Surface
-                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.surface))
-                Box(
-                    modifier = Modifier.size(1.dp).background(
-                        MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.surface)
+                Box(modifier = Modifier
+                    .size(1.dp)
+                    .background(MaterialTheme.colorScheme.surfaceContainer))
+                Box(modifier = Modifier
+                    .size(1.dp)
+                    .background(
+                        MaterialTheme.colorScheme
+                            .contentColorFor(MaterialTheme.colorScheme.surfaceContainer)
                     )
                 )
 
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
index 2058328..9a49f2e 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToDismissBoxSample.kt
@@ -127,7 +127,7 @@
                             modifier = Modifier
                                 .height(40.dp)
                                 .background(
-                                    color = MaterialTheme.colorScheme.surface,
+                                    color = MaterialTheme.colorScheme.surfaceContainer,
                                     shape = CircleShape
                                 )
                                 .padding(horizontal = 12.dp),
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
index ed0430c..a7e67ae 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
@@ -400,7 +400,7 @@
     fun gives_enabled_filled_tonal_base_button_correct_colors() {
         rule.verifyButtonColors(
             status = Status.Enabled,
-            expectedContainerColor = { MaterialTheme.colorScheme.surface },
+            expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
             expectedContentColor = { MaterialTheme.colorScheme.onSurface },
             content = { FilledTonalButton(Status.Enabled) }
         )
@@ -1049,7 +1049,7 @@
     val padding = 0.dp
 
     setContentWithTheme {
-        background = MaterialTheme.colorScheme.surface
+        background = MaterialTheme.colorScheme.surfaceContainer
         Box(Modifier.background(background)) {
             buttonColor = (colors().containerPainter(true) as ColorPainter).color
             if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
index d9d875a..4c33129 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
@@ -398,7 +398,7 @@
         rule.verifyIconButtonColors(
             status = Status.Enabled,
             colors = { IconButtonDefaults.filledTonalIconButtonColors() },
-            expectedContainerColor = { MaterialTheme.colorScheme.surface },
+            expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
             expectedContentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
         )
     }
@@ -536,7 +536,7 @@
     val padding = 0.dp
 
     setContentWithTheme {
-        background = MaterialTheme.colorScheme.surface
+        background = MaterialTheme.colorScheme.surfaceContainer
         Box(Modifier.background(background)) {
             buttonColor = colors().containerColor(true)
             if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index e8d1930..8d6e1bc 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -414,7 +414,7 @@
             status = Status.Enabled,
             checked = false,
             colors = { IconButtonDefaults.iconToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.surface },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer },
             contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
         )
 
@@ -425,7 +425,7 @@
             status = Status.Disabled,
             checked = false,
             colors = { IconButtonDefaults.iconToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() },
             contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
         )
 
@@ -507,7 +507,7 @@
                     uncheckedContentColor = overrideColor
                 )
             },
-            containerColor = { MaterialTheme.colorScheme.surface },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer },
             contentColor = { overrideColor }
         )
     }
@@ -584,7 +584,7 @@
                 )
             },
             contentColor = { overrideColor },
-            containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() }
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() }
         )
     }
 
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
index 9a176be..bb5a14a 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsScreenshotTest.kt
@@ -99,6 +99,6 @@
             .background(
                 MaterialTheme.colorScheme.primary
                     .copy(alpha = 0.5f)
-                    .compositeOver(MaterialTheme.colorScheme.surface)
+                    .compositeOver(MaterialTheme.colorScheme.surfaceContainer)
             )
 }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index 87e1a71..f00b3af 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -387,7 +387,7 @@
         rule.verifyTextButtonColors(
             status = Status.Enabled,
             colors = { TextButtonDefaults.filledTonalTextButtonColors() },
-            expectedContainerColor = { MaterialTheme.colorScheme.surface },
+            expectedContainerColor = { MaterialTheme.colorScheme.surfaceContainer },
             expectedContentColor = { MaterialTheme.colorScheme.onSurface }
         )
     }
@@ -530,7 +530,7 @@
     val padding = 0.dp
 
     setContentWithTheme {
-        background = MaterialTheme.colorScheme.surface
+        background = MaterialTheme.colorScheme.surfaceContainer
         Box(Modifier.background(background)) {
             buttonColor = colors().containerColor(true)
             if (buttonColor == Color.Transparent) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
index d1202bbb..08d4b07 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
@@ -404,7 +404,7 @@
             status = Status.Enabled,
             checked = false,
             colors = { TextButtonDefaults.textToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.surface },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer },
             contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
         )
 
@@ -415,7 +415,7 @@
             status = Status.Disabled,
             checked = false,
             colors = { TextButtonDefaults.textToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer.toDisabledColor() },
             contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
         )
 
@@ -497,7 +497,7 @@
                     uncheckedContentColor = override
                 )
             },
-            containerColor = { MaterialTheme.colorScheme.surface },
+            containerColor = { MaterialTheme.colorScheme.surfaceContainer },
             contentColor = { override }
         )
     }
@@ -584,7 +584,7 @@
             },
             contentColor = { override },
             containerColor = {
-                MaterialTheme.colorScheme.surface.toDisabledColor()
+                MaterialTheme.colorScheme.surfaceContainer.toDisabledColor()
             }
         )
     }
@@ -671,6 +671,7 @@
             }
         )
     }
+
     @RequiresApi(Build.VERSION_CODES.O)
     private fun ComposeContentTestRule.isShape(
         shape: Shape = CircleShape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
index 95f5df1..5f43605 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleButtonTest.kt
@@ -816,7 +816,7 @@
     checked: Boolean
 ): Color {
     return if (checked) MaterialTheme.colorScheme.primaryContainer
-    else MaterialTheme.colorScheme.surface
+    else MaterialTheme.colorScheme.surfaceContainer
 }
 
 @Composable
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
index 14cdb66..85d3f0e 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ToggleControlsScreenshotTest.kt
@@ -174,6 +174,6 @@
             .background(
                 MaterialTheme.colorScheme.primary
                     .copy(alpha = 0.5f)
-                    .compositeOver(MaterialTheme.colorScheme.surface)
+                    .compositeOver(MaterialTheme.colorScheme.surfaceContainer)
             )
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
index 9b830dc..3256c14 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
@@ -28,53 +28,56 @@
 /**
  * A [ColorScheme] holds all the named color parameters for a [MaterialTheme].
  *
- * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI
- * elements and surfaces from one another.
+ * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI elements
+ * and surfaces from one another.
  *
  * The Material color system and custom schemes provide default values for color as a starting point
  * for customization.
  *
- * To learn more about color schemes,
- * see [Material Design Color System](https://m3.material.io/styles/color/the-color-system/color-roles).
+ * To learn more about color schemes, see
+ * [Material Design Color System](https://m3.material.io/styles/color/the-color-system/color-roles).
  *
  * @property primary The primary color is the color displayed most frequently across your app’s
- * screens and components.
+ *   screens and components.
  * @property primaryDim is less prominent than [primary] for component backgrounds
  * @property primaryContainer is a standout container color for key components.
  * @property onPrimary Color used for text and icons displayed on top of the primary color.
  * @property onPrimaryContainer The color (and state variants) that should be used for content on
- * top of [primaryContainer].
+ *   top of [primaryContainer].
  * @property secondary The secondary color provides more ways to accent and distinguish your
- * product.
+ *   product.
  * @property secondaryDim is less prominent than [secondary] for component backgrounds.
  * @property secondaryContainer A tonal color to be used in containers.
  * @property onSecondary Color used for text and icons displayed on top of the secondary color.
  * @property onSecondaryContainer The color (and state variants) that should be used for content on
- * top of [secondaryContainer].
- * @property tertiary The tertiary color that can be used to balance primary and secondary
- * colors, or bring heightened attention to an element.
- * @property tertiaryDim A less prominent tertiary color that can be used to balance
- * primary and secondary colors, or bring heightened attention to an element.
+ *   top of [secondaryContainer].
+ * @property tertiary The tertiary color that can be used to balance primary and secondary colors,
+ *   or bring heightened attention to an element.
+ * @property tertiaryDim A less prominent tertiary color that can be used to balance primary and
+ *   secondary colors, or bring heightened attention to an element.
  * @property tertiaryContainer A tonal color to be used in containers.
  * @property onTertiary Color used for text and icons displayed on top of the tertiary color.
  * @property onTertiaryContainer The color (and state variants) that should be used for content on
- * top of [tertiaryContainer].
- * @property surfaceDim A surface color used for large containment components
- * such as Card and Button with low prominence.
- * @property surface The main surface color that affect surfaces of components with large
- * containment areas, such as Card and Button.
- * @property surfaceBright A surface color used for large containment components
- * such Card and Button with high prominence.
+ *   top of [tertiaryContainer].
+ * @property surfaceContainerLow A surface color used for large containment components such as Card
+ *   and Button with low prominence.
+ * @property surfaceContainer The main surface color that affect surfaces of components with large
+ *   containment areas, such as Card and Button.
+ * @property surfaceContainerHigh A surface color used for large containment components such Card
+ *   and Button with high prominence.
  * @property onSurface Color used for text and icons displayed on top of the surface color.
- * @property onSurfaceVariant The color for secondary text and icons on top of
- * [surface].
- * @property outline The main color for primary outline components.
- * The outline color role adds contrast for accessibility purposes.
+ * @property onSurfaceVariant The color for secondary text and icons on top of [surfaceContainer].
+ * @property outline The main color for primary outline components. The outline color role adds
+ *   contrast for accessibility purposes.
  * @property outlineVariant The secondary color for secondary outline components.
  * @property background The background color that appears behind other content.
  * @property onBackground Color used for text and icons displayed on top of the background color.
- * @property error The error color is used to indicate errors.
+ * @property error Color that indicates remove, delete, close or dismiss actions, such as Swipe to
+ *   Reveal. Added as an errorContainer alternative that is slightly less alarming and urgent color.
  * @property onError Color used for text and icons displayed on top of the error color.
+ * @property errorContainer Color that indicates errors or emergency actions, such as safety alerts.
+ *   This color is for use-cases that are more alarming and urgent than the error color.
+ * @property onErrorContainer Color used for text and icons on the errorContainer color.
  */
 @Immutable
 @Stable
@@ -94,9 +97,9 @@
     val tertiaryContainer: Color = ColorTokens.TertiaryContainer,
     val onTertiary: Color = ColorTokens.OnTertiary,
     val onTertiaryContainer: Color = ColorTokens.OnTertiaryContainer,
-    val surfaceDim: Color = ColorTokens.SurfaceDim,
-    val surface: Color = ColorTokens.Surface,
-    val surfaceBright: Color = ColorTokens.SurfaceBright,
+    val surfaceContainerLow: Color = ColorTokens.SurfaceContainerLow,
+    val surfaceContainer: Color = ColorTokens.SurfaceContainer,
+    val surfaceContainerHigh: Color = ColorTokens.SurfaceContainerHigh,
     val onSurface: Color = ColorTokens.OnSurface,
     val onSurfaceVariant: Color = ColorTokens.OnSurfaceVariant,
     val outline: Color = ColorTokens.Outline,
@@ -105,10 +108,10 @@
     val onBackground: Color = ColorTokens.OnBackground,
     val error: Color = ColorTokens.Error,
     val onError: Color = ColorTokens.OnError,
+    val errorContainer: Color = ColorTokens.ErrorContainer,
+    val onErrorContainer: Color = ColorTokens.OnErrorContainer,
 ) {
-    /**
-     * Returns a copy of this Colors, optionally overriding some of the values.
-     */
+    /** Returns a copy of this Colors, optionally overriding some of the values. */
     fun copy(
         primary: Color = this.primary,
         primaryDim: Color = this.primaryDim,
@@ -125,9 +128,9 @@
         tertiaryContainer: Color = this.tertiaryContainer,
         onTertiary: Color = this.onTertiary,
         onTertiaryContainer: Color = this.onTertiaryContainer,
-        surfaceDim: Color = this.surfaceDim,
-        surface: Color = this.surface,
-        surfaceBright: Color = this.surfaceBright,
+        surfaceContainerLow: Color = this.surfaceContainerLow,
+        surfaceContainer: Color = this.surfaceContainer,
+        surfaceContainerHigh: Color = this.surfaceContainerHigh,
         onSurface: Color = this.onSurface,
         onSurfaceVariant: Color = this.onSurfaceVariant,
         outline: Color = this.outline,
@@ -135,35 +138,40 @@
         background: Color = this.background,
         onBackground: Color = this.onBackground,
         error: Color = this.error,
-        onError: Color = this.onError
-    ): ColorScheme = ColorScheme(
-        primary = primary,
-        primaryDim = primaryDim,
-        primaryContainer = primaryContainer,
-        >
-        >
-        secondary = secondary,
-        secondaryDim = secondaryDim,
-        secondaryContainer = secondaryContainer,
-        >
-        >
-        tertiary = tertiary,
-        tertiaryDim = tertiaryDim,
-        tertiaryContainer = tertiaryContainer,
-        >
-        >
-        surfaceDim = surfaceDim,
-        surface = surface,
-        surfaceBright = surfaceBright,
-        >
-        >
-        outline = outline,
-        outlineVariant = outlineVariant,
-        background = background,
-        >
-        error = error,
-        >
-    )
+        onError: Color = this.onError,
+        errorContainer: Color = this.errorContainer,
+        onErrorContainer: Color = this.onErrorContainer,
+    ): ColorScheme =
+        ColorScheme(
+            primary = primary,
+            primaryDim = primaryDim,
+            primaryContainer = primaryContainer,
+            >
+            >
+            secondary = secondary,
+            secondaryDim = secondaryDim,
+            secondaryContainer = secondaryContainer,
+            >
+            >
+            tertiary = tertiary,
+            tertiaryDim = tertiaryDim,
+            tertiaryContainer = tertiaryContainer,
+            >
+            >
+            surfaceContainerLow = surfaceContainerLow,
+            surfaceContainer = surfaceContainer,
+            surfaceContainerHigh = surfaceContainerHigh,
+            >
+            >
+            outline = outline,
+            outlineVariant = outlineVariant,
+            background = background,
+            >
+            error = error,
+            >
+            errorContainer = errorContainer,
+            >
+        )
 
     override fun toString(): String {
         return "Colors(" +
@@ -182,17 +190,18 @@
             "tertiaryContainer=$tertiaryContainer, " +
             " " +
             " " +
-            "surfaceDim=$surfaceDim, " +
-            "surface=$surface, " +
-            "surfaceBright=$surfaceBright, " +
+            "surfaceContainerLow=$surfaceContainerLow, " +
+            "surfaceContainer=$surfaceContainer, " +
+            "surfaceContainerHigh=$surfaceContainerHigh, " +
             " " +
             " " +
             "outline=$outline, " +
             "outlineVariant=$outlineVariant, " +
             "background=$background, " +
             " " +
-            "error=$error, " +
-            " +
+            " +
+            "errorContainer=$errorContainer, " +
+            " +
             ")"
     }
 
@@ -229,8 +238,8 @@
 }
 
 /**
- * The Material color system contains pairs of colors that are typically used for the background
- * and content color inside a component. For example, a Button typically uses `primary` for its
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
  * background, and `onPrimary` for the color of its content (usually text or iconography).
  *
  * This function tries to match the provided [backgroundColor] to a 'background' color in this
@@ -241,53 +250,56 @@
  * [Color.Unspecified].
  *
  * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
- * the theme's [ColorScheme], then returns [Color.Unspecified].
- *
+ *   the theme's [ColorScheme], then returns [Color.Unspecified].
  * @see contentColorFor
  */
 fun ColorScheme.contentColorFor(backgroundColor: Color): Color {
     return when (backgroundColor) {
-        primary, primaryDim -> onPrimary
+        primary,
+        primaryDim -> onPrimary
         primaryContainer -> onPrimaryContainer
-        secondary, secondaryDim -> onSecondary
+        secondary,
+        secondaryDim -> onSecondary
         secondaryContainer -> onSecondaryContainer
-        tertiary, tertiaryDim -> onTertiary
+        tertiary,
+        tertiaryDim -> onTertiary
         tertiaryContainer -> onTertiaryContainer
-        surface, surfaceDim, surfaceBright -> onSurface
+        surfaceContainer,
+        surfaceContainerLow,
+        surfaceContainerHigh -> onSurface
         background -> onBackground
         error -> onError
+        errorContainer -> onErrorContainer
         else -> Color.Unspecified
     }
 }
 
 /**
- * The Material color system contains pairs of colors that are typically used for the background
- * and content color inside a component. For example, a Button typically uses `primary` for its
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
  * background, and `onPrimary` for the color of its content (usually text or iconography).
  *
  * This function tries to match the provided [backgroundColor] to a 'background' color in this
  * [ColorScheme], and then will return the corresponding color used for content. For example, when
  * [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
  *
- * If [backgroundColor] does not match a background color in the theme, this will return
- * the current value of [LocalContentColor] as a best-effort color.
+ * If [backgroundColor] does not match a background color in the theme, this will return the current
+ * value of [LocalContentColor] as a best-effort color.
  *
  * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
- * the theme's [ColorScheme], then returns the current value of [LocalContentColor].
- *
+ *   the theme's [ColorScheme], then returns the current value of [LocalContentColor].
  * @see ColorScheme.contentColorFor
  */
 @Composable
 @ReadOnlyComposable
 fun contentColorFor(backgroundColor: Color): Color =
-    MaterialTheme.colorScheme
-        .contentColorFor(backgroundColor)
-        .takeOrElse { LocalContentColor.current }
+    MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse {
+        LocalContentColor.current
+    }
 
 /**
  * Helper function for component color tokens. Here is an example on how to use component color
- * tokens:
- * ``MaterialTheme.colorScheme.fromToken(FilledButtonTokens.ContainerColor)``
+ * tokens: ``MaterialTheme.colorScheme.fromToken(FilledButtonTokens.ContainerColor)``
  */
 internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color {
     return when (value) {
@@ -306,9 +318,9 @@
         ColorSchemeKeyTokens.TertiaryContainer -> tertiaryContainer
         ColorSchemeKeyTokens.OnTertiary -> onTertiary
         ColorSchemeKeyTokens.OnTertiaryContainer -> onTertiaryContainer
-        ColorSchemeKeyTokens.SurfaceDim -> surfaceDim
-        ColorSchemeKeyTokens.Surface -> surface
-        ColorSchemeKeyTokens.SurfaceBright -> surfaceBright
+        ColorSchemeKeyTokens.SurfaceContainerLow -> surfaceContainerLow
+        ColorSchemeKeyTokens.SurfaceContainer -> surfaceContainer
+        ColorSchemeKeyTokens.SurfaceContainerHigh -> surfaceContainerHigh
         ColorSchemeKeyTokens.OnSurface -> onSurface
         ColorSchemeKeyTokens.OnSurfaceVariant -> onSurfaceVariant
         ColorSchemeKeyTokens.Outline -> outline
@@ -317,33 +329,33 @@
         ColorSchemeKeyTokens.OnBackground -> onBackground
         ColorSchemeKeyTokens.Error -> error
         ColorSchemeKeyTokens.OnError -> onError
+        ColorSchemeKeyTokens.ErrorContainer -> errorContainer
+        ColorSchemeKeyTokens.OnErrorContainer -> onErrorContainer
     }
 }
 
 /**
  * CompositionLocal used to pass [ColorScheme] down the tree.
  *
- * Setting the value here is typically done as part of [MaterialTheme].
- * To retrieve the current value of this CompositionLocal, use
- * [MaterialTheme.colorScheme].
+ * Setting the value here is typically done as part of [MaterialTheme]. To retrieve the current
+ * value of this CompositionLocal, use [MaterialTheme.colorScheme].
  */
 internal val LocalColorScheme = staticCompositionLocalOf<ColorScheme> { ColorScheme() }
 
 /**
  * Convert given color to disabled color.
+ *
  * @param disabledAlpha Alpha used to represent disabled colors.
  */
 internal fun Color.toDisabledColor(disabledAlpha: Float = DisabledContentAlpha) =
     this.copy(alpha = this.alpha * disabledAlpha)
 
 /**
- * Converts a color token key to the local color scheme provided by the theme.
- * The color references the [LocalColorScheme].
+ * Converts a color token key to the local color scheme provided by the theme. The color references
+ * the [LocalColorScheme].
  */
 internal val ColorSchemeKeyTokens.value: Color
-    @ReadOnlyComposable
-    @Composable
-    get() = MaterialTheme.colorScheme.fromToken(this)
+    @ReadOnlyComposable @Composable get() = MaterialTheme.colorScheme.fromToken(this)
 
 internal const val DisabledContentAlpha = 0.38f
 internal const val DisabledContainerAlpha = 0.12f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
index f05bed6..0f79f2e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentColor.kt
@@ -21,8 +21,9 @@
 
 /**
  * CompositionLocal containing the preferred content color for a given position in the hierarchy.
- * This typically represents the `on` color for a color in [ColorScheme]. For example, if the background
- * color is [ColorScheme.surface], this color is typically set to [ColorScheme.onSurface].
+ * This typically represents the `on` color for a color in [ColorScheme]. For example, if the
+ * background color is [ColorScheme.surfaceContainer], this color is typically set to
+ * [ColorScheme.onSurface].
  *
  * This color should be used for any typography / iconography, to ensure that the color of these
  * adjusts when the background color changes. For example, on a dark background, text should be
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index a02f410..133d447 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -329,7 +329,7 @@
      */
     @Composable
     fun colors(
-        containerColor: Color = MaterialTheme.colorScheme.surface,
+        containerColor: Color = MaterialTheme.colorScheme.surfaceContainer,
         buttonIconColor: Color = MaterialTheme.colorScheme.secondary,
         selectedBarColor: Color = MaterialTheme.colorScheme.primary,
         unselectedBarColor: Color = MaterialTheme.colorScheme.background.copy(alpha = 0.3f),
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
index a7c48bc..b44ce9b 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleControls.kt
@@ -458,7 +458,7 @@
         checkedTrackBorderColor: Color = MaterialTheme.colorScheme.primary,
         uncheckedThumbColor: Color = MaterialTheme.colorScheme.outline,
         uncheckedThumbIconColor: Color = Color.Transparent,
-        uncheckedTrackColor: Color = MaterialTheme.colorScheme.surface,
+        uncheckedTrackColor: Color = MaterialTheme.colorScheme.surfaceContainer,
         uncheckedTrackBorderColor: Color = MaterialTheme.colorScheme.outline,
         disabledCheckedThumbColor: Color = MaterialTheme.colorScheme.background.toDisabledColor(),
         disabledCheckedThumbIconColor: Color =
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
index 93778f2..f0cc446 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/CardTokens.kt
@@ -25,7 +25,7 @@
     val AppImageSize = 16.0.dp
     val AppNameColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val AppNameTypography = TypographyKeyTokens.LabelSmall
-    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val ContentTypography = TypographyKeyTokens.BodyLarge
     val Shape = ShapeKeyTokens.CornerLarge
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
index 9bbfe57..3dbbc0c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorSchemeKeyTokens.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// VERSION: v0_7
+// VERSION: v0_42
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.wear.compose.material3.tokens
@@ -22,8 +22,10 @@
 internal enum class ColorSchemeKeyTokens {
     Background,
     Error,
+    ErrorContainer,
     OnBackground,
     OnError,
+    OnErrorContainer,
     OnPrimary,
     OnPrimaryContainer,
     OnSecondary,
@@ -40,9 +42,9 @@
     Secondary,
     SecondaryContainer,
     SecondaryDim,
-    Surface,
-    SurfaceBright,
-    SurfaceDim,
+    SurfaceContainer,
+    SurfaceContainerHigh,
+    SurfaceContainerLow,
     Tertiary,
     TertiaryContainer,
     TertiaryDim,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
index e3be595..68dc43f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ColorTokens.kt
@@ -14,16 +14,18 @@
  * limitations under the License.
  */
 
-// VERSION: v0_8
+// VERSION: v0_42
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.wear.compose.material3.tokens
 
 internal object ColorTokens {
     val Background = PaletteTokens.Neutral0
-    val Error = PaletteTokens.Error65
+    val Error = PaletteTokens.Error80
+    val ErrorContainer = PaletteTokens.Error70
     val >
-    val >
+    val >
+    val >
     val >
     val >
     val >
@@ -40,9 +42,9 @@
     val Secondary = PaletteTokens.Secondary90
     val SecondaryContainer = PaletteTokens.Secondary30
     val SecondaryDim = PaletteTokens.Secondary80
-    val Surface = PaletteTokens.Neutral20
-    val SurfaceBright = PaletteTokens.Neutral30
-    val SurfaceDim = PaletteTokens.Neutral15
+    val SurfaceContainer = PaletteTokens.Neutral20
+    val SurfaceContainerHigh = PaletteTokens.Neutral30
+    val SurfaceContainerLow = PaletteTokens.Neutral15
     val Tertiary = PaletteTokens.Tertiary90
     val TertiaryContainer = PaletteTokens.Tertiary30
     val TertiaryDim = PaletteTokens.Tertiary80
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
index 1341a0b..9275864 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalButtonTokens.kt
@@ -22,7 +22,7 @@
 import androidx.compose.ui.unit.dp
 
 internal object FilledTonalButtonTokens {
-    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val ContainerHeight = 52.0.dp
     val ContainerShape = ShapeKeyTokens.CornerLarge
     val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
index e2c42f5..f4dc76f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
@@ -20,10 +20,10 @@
 package androidx.wear.compose.material3.tokens
 
 internal object FilledTonalIconButtonTokens {
-  val ContainerColor = ColorSchemeKeyTokens.Surface
-  val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
-  val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
-  val DisabledContainerOpacity = 0.12f
-  val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
-  val DisabledContentOpacity = 0.38f
+    val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
+    val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
+    val DisabledContainerOpacity = 0.12f
+    val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+    val DisabledContentOpacity = 0.38f
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
index a77dd0a..8459b18 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalTextButtonTokens.kt
@@ -20,7 +20,7 @@
 package androidx.wear.compose.material3.tokens
 
 internal object FilledTonalTextButtonTokens {
-    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val ContainerShape = ShapeKeyTokens.CornerFull
     val ContentColor = ColorSchemeKeyTokens.OnSurface
     val ContentFont = TypographyKeyTokens.LabelMedium
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
index b6ae374..c626ab9 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
@@ -26,10 +26,10 @@
     val DisabledCheckedContainerOpacity = 0.38f
     val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
     val DisabledCheckedContentOpacity = 0.38f
-    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUncheckedContainerOpacity = 0.38f
     val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val DisabledUncheckedContentOpacity = 0.38f
-    val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
index 1f8188e..51a8342 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ImageButtonTokens.kt
@@ -20,7 +20,7 @@
 package androidx.wear.compose.material3.tokens
 
 internal object ImageButtonTokens {
-    val BackgroundImageGradientColor = ColorSchemeKeyTokens.Surface
+    val BackgroundImageGradientColor = ColorSchemeKeyTokens.SurfaceContainer
     val ContentColor = ColorSchemeKeyTokens.OnSurface
     val DisabledContentOpacity = 0.38f
     val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
index 65e3d32..6c79d71 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/PaletteTokens.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// VERSION: v0_8
+// VERSION: v0_44
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.wear.compose.material3.tokens
@@ -23,78 +23,79 @@
 
 internal object PaletteTokens {
     val Error0 = Color(red = 0, green = 0, blue = 0)
-    val Error10 = Color(red = 65, green = 0, blue = 2)
+    val Error10 = Color(red = 65, green = 14, blue = 11)
     val Error100 = Color(red = 255, green = 255, blue = 255)
-    val Error20 = Color(red = 104, green = 1, blue = 7)
-    val Error30 = Color(red = 137, green = 29, blue = 26)
-    val Error40 = Color(red = 170, green = 53, blue = 47)
-    val Error50 = Color(red = 203, green = 77, blue = 69)
-    val Error60 = Color(red = 236, green = 102, blue = 91)
-    val Error65 = Color(red = 253, green = 114, blue = 103)
-    val Error70 = Color(red = 255, green = 137, blue = 126)
-    val Error80 = Color(red = 255, green = 180, blue = 172)
-    val Error85 = Color(red = 255, green = 199, blue = 193)
-    val Error90 = Color(red = 255, green = 218, blue = 214)
-    val Error95 = Color(red = 255, green = 237, blue = 234)
+    val Error20 = Color(red = 96, green = 20, blue = 16)
+    val Error30 = Color(red = 140, green = 29, blue = 24)
+    val Error40 = Color(red = 179, green = 38, blue = 30)
+    val Error5 = Color(red = 33, green = 7, blue = 6)
+    val Error50 = Color(red = 220, green = 54, blue = 46)
+    val Error60 = Color(red = 228, green = 105, blue = 98)
+    val Error65 = Color(red = 232, green = 126, blue = 120)
+    val Error70 = Color(red = 236, green = 146, blue = 142)
+    val Error80 = Color(red = 242, green = 184, blue = 181)
+    val Error85 = Color(red = 245, green = 203, blue = 200)
+    val Error90 = Color(red = 249, green = 222, blue = 220)
+    val Error95 = Color(red = 252, green = 238, blue = 238)
     val Neutral0 = Color(red = 0, green = 0, blue = 0)
-    val Neutral10 = Color(red = 31, green = 31, blue = 31)
+    val Neutral10 = Color(red = 29, green = 26, blue = 38)
     val Neutral100 = Color(red = 255, green = 255, blue = 255)
-    val Neutral15 = Color(red = 37, green = 38, blue = 38)
-    val Neutral20 = Color(red = 48, green = 48, blue = 48)
-    val Neutral30 = Color(red = 71, green = 71, blue = 71)
-    val Neutral40 = Color(red = 94, green = 94, blue = 94)
-    val Neutral50 = Color(red = 117, green = 117, blue = 117)
-    val Neutral60 = Color(red = 143, green = 143, blue = 143)
-    val Neutral70 = Color(red = 171, green = 171, blue = 171)
-    val Neutral80 = Color(red = 199, green = 199, blue = 199)
-    val Neutral90 = Color(red = 227, green = 227, blue = 227)
-    val Neutral95 = Color(red = 242, green = 242, blue = 242)
+    val Neutral15 = Color(red = 39, green = 36, blue = 48)
+    val Neutral20 = Color(red = 51, green = 46, blue = 60)
+    val Neutral30 = Color(red = 73, green = 68, blue = 83)
+    val Neutral40 = Color(red = 97, green = 92, blue = 107)
+    val Neutral50 = Color(red = 122, green = 116, blue = 132)
+    val Neutral60 = Color(red = 148, green = 142, blue = 159)
+    val Neutral70 = Color(red = 175, green = 168, blue = 185)
+    val Neutral80 = Color(red = 203, green = 195, blue = 213)
+    val Neutral90 = Color(red = 232, green = 223, blue = 242)
+    val Neutral95 = Color(red = 246, green = 237, blue = 255)
     val NeutralVariant0 = Color(red = 0, green = 0, blue = 0)
-    val NeutralVariant10 = Color(red = 25, green = 29, blue = 28)
+    val NeutralVariant10 = Color(red = 29, green = 26, blue = 35)
     val NeutralVariant100 = Color(red = 255, green = 255, blue = 255)
-    val NeutralVariant20 = Color(red = 45, green = 49, blue = 47)
-    val NeutralVariant30 = Color(red = 68, green = 71, blue = 70)
-    val NeutralVariant40 = Color(red = 92, green = 95, blue = 94)
-    val NeutralVariant50 = Color(red = 116, green = 119, blue = 117)
-    val NeutralVariant60 = Color(red = 142, green = 145, blue = 143)
-    val NeutralVariant70 = Color(red = 169, green = 172, blue = 170)
-    val NeutralVariant80 = Color(red = 196, green = 199, blue = 197)
-    val NeutralVariant90 = Color(red = 225, green = 227, blue = 225)
-    val NeutralVariant95 = Color(red = 239, green = 242, blue = 239)
+    val NeutralVariant20 = Color(red = 50, green = 47, blue = 56)
+    val NeutralVariant30 = Color(red = 73, green = 69, blue = 79)
+    val NeutralVariant40 = Color(red = 97, green = 93, blue = 103)
+    val NeutralVariant50 = Color(red = 122, green = 117, blue = 128)
+    val NeutralVariant60 = Color(red = 148, green = 143, blue = 154)
+    val NeutralVariant70 = Color(red = 175, green = 169, blue = 181)
+    val NeutralVariant80 = Color(red = 202, green = 196, blue = 208)
+    val NeutralVariant90 = Color(red = 231, green = 224, blue = 236)
+    val NeutralVariant95 = Color(red = 245, green = 238, blue = 251)
     val Primary0 = Color(red = 0, green = 0, blue = 0)
-    val Primary10 = Color(red = 4, green = 30, blue = 73)
+    val Primary10 = Color(red = 33, green = 15, blue = 72)
     val Primary100 = Color(red = 255, green = 255, blue = 255)
-    val Primary20 = Color(red = 4, green = 30, blue = 73)
-    val Primary30 = Color(red = 8, green = 66, blue = 160)
-    val Primary40 = Color(red = 11, green = 87, blue = 208)
-    val Primary50 = Color(red = 27, green = 110, blue = 243)
-    val Primary60 = Color(red = 76, green = 141, blue = 246)
-    val Primary70 = Color(red = 124, green = 172, blue = 248)
-    val Primary80 = Color(red = 168, green = 199, blue = 250)
-    val Primary90 = Color(red = 211, green = 227, blue = 253)
-    val Primary95 = Color(red = 236, green = 243, blue = 254)
+    val Primary20 = Color(red = 55, green = 38, blue = 94)
+    val Primary30 = Color(red = 77, green = 61, blue = 118)
+    val Primary40 = Color(red = 102, green = 85, blue = 144)
+    val Primary50 = Color(red = 127, green = 109, blue = 170)
+    val Primary60 = Color(red = 153, green = 135, blue = 198)
+    val Primary70 = Color(red = 180, green = 161, blue = 226)
+    val Primary80 = Color(red = 208, green = 188, blue = 255)
+    val Primary90 = Color(red = 233, green = 221, blue = 255)
+    val Primary95 = Color(red = 246, green = 237, blue = 255)
     val Secondary0 = Color(red = 0, green = 0, blue = 0)
-    val Secondary10 = Color(red = 0, green = 29, blue = 53)
+    val Secondary10 = Color(red = 30, green = 24, blue = 46)
     val Secondary100 = Color(red = 255, green = 255, blue = 255)
-    val Secondary20 = Color(red = 0, green = 51, blue = 85)
-    val Secondary30 = Color(red = 0, green = 51, blue = 85)
-    val Secondary40 = Color(red = 0, green = 99, blue = 155)
-    val Secondary50 = Color(red = 4, green = 125, blue = 183)
-    val Secondary60 = Color(red = 57, green = 152, blue = 211)
-    val Secondary70 = Color(red = 90, green = 179, blue = 240)
-    val Secondary80 = Color(red = 127, green = 207, blue = 255)
-    val Secondary90 = Color(red = 194, green = 231, blue = 255)
-    val Secondary95 = Color(red = 223, green = 243, blue = 255)
+    val Secondary20 = Color(red = 51, green = 45, blue = 68)
+    val Secondary30 = Color(red = 74, green = 67, blue = 91)
+    val Secondary40 = Color(red = 98, green = 90, blue = 116)
+    val Secondary50 = Color(red = 123, green = 115, blue = 141)
+    val Secondary60 = Color(red = 150, green = 140, blue = 168)
+    val Secondary70 = Color(red = 177, green = 167, blue = 195)
+    val Secondary80 = Color(red = 204, green = 194, blue = 223)
+    val Secondary90 = Color(red = 233, green = 222, blue = 252)
+    val Secondary95 = Color(red = 246, green = 237, blue = 255)
     val Tertiary0 = Color(red = 0, green = 0, blue = 0)
-    val Tertiary10 = Color(red = 7, green = 39, blue = 17)
+    val Tertiary10 = Color(red = 46, green = 21, blue = 0)
     val Tertiary100 = Color(red = 255, green = 255, blue = 255)
-    val Tertiary20 = Color(red = 7, green = 39, blue = 17)
-    val Tertiary30 = Color(red = 15, green = 82, blue = 35)
-    val Tertiary40 = Color(red = 20, green = 108, blue = 46)
-    val Tertiary50 = Color(red = 25, green = 134, blue = 57)
-    val Tertiary60 = Color(red = 30, green = 164, blue = 70)
-    val Tertiary70 = Color(red = 55, green = 190, blue = 95)
-    val Tertiary80 = Color(red = 109, green = 213, blue = 140)
-    val Tertiary90 = Color(red = 196, green = 238, blue = 208)
-    val Tertiary95 = Color(red = 231, green = 248, blue = 237)
+    val Tertiary20 = Color(red = 76, green = 39, blue = 0)
+    val Tertiary30 = Color(red = 108, green = 58, blue = 3)
+    val Tertiary40 = Color(red = 136, green = 81, blue = 27)
+    val Tertiary50 = Color(red = 165, green = 105, blue = 49)
+    val Tertiary60 = Color(red = 195, green = 130, blue = 72)
+    val Tertiary70 = Color(red = 226, green = 156, blue = 95)
+    val Tertiary80 = Color(red = 255, green = 183, blue = 122)
+    val Tertiary90 = Color(red = 255, green = 220, blue = 194)
+    val Tertiary95 = Color(red = 255, green = 238, blue = 226)
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
index 85d5654..71802e6 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
@@ -25,7 +25,7 @@
     val DisabledSelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val DisabledSelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val DisabledSelectedSecondaryLabelOpacity = 0.8f
-    val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
     val DisabledUnselectedIconColor = ColorSchemeKeyTokens.Primary
     val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
@@ -37,7 +37,7 @@
     val SelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val SelectedSecondaryLabelOpacity = 0.8f
     val Shape = ShapeKeyTokens.CornerLarge
-    val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+    val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
     val UnselectedIconColor = ColorSchemeKeyTokens.Primary
     val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
index 91b1135..afa6d71 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
@@ -26,10 +26,10 @@
     val DisabledSelectedSecondaryLabelOpacity = 0.8f
     val DisabledSelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
     val DisabledSelectedSplitContainerOpacity = 0.15f
-    val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
     val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val DisabledUnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+    val DisabledUnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
     val LabelFont = TypographyKeyTokens.LabelMedium
     val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
     val SelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
@@ -39,8 +39,8 @@
     val SelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
     val SelectedSplitContainerOpacity = 0.15f
     val Shape = ShapeKeyTokens.CornerLarge
-    val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+    val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
     val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val UnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+    val UnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
index 6241bb6..73a0847 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitToggleButtonTokens.kt
@@ -34,14 +34,14 @@
     val DisabledCheckedSplitContainerColor = ColorSchemeKeyTokens.Primary
     val DisabledCheckedSplitContainerOpacity = 0.15f
     val DisabledOpacity = 0.38f
-    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurface
     val DisabledUncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val DisabledUncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+    val DisabledUncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
     val LabelFont = TypographyKeyTokens.LabelMedium
     val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
-    val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UncheckedContentColor = ColorSchemeKeyTokens.OnSurface
     val UncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val UncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+    val UncheckedSplitContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
index 3e04ba6..fb0b3c2 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TextToggleButtonTokens.kt
@@ -28,10 +28,10 @@
     val DisabledCheckedContainerOpacity = 0.38f
     val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
     val DisabledCheckedContentOpacity = 0.38f
-    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUncheckedContainerOpacity = 0.38f
     val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val DisabledUncheckedContentOpacity = 0.38f
-    val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
index c211e57..f4c0eef 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/ToggleButtonTokens.kt
@@ -32,13 +32,13 @@
     val DisabledCheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val DisabledCheckedSecondaryLabelOpacity = 0.8f
     val DisabledOpacity = 0.38f
-    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurface
     val DisabledUncheckedIconColor = ColorSchemeKeyTokens.Primary
     val DisabledUncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val LabelFont = TypographyKeyTokens.LabelMedium
     val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
-    val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val UncheckedContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val UncheckedContentColor = ColorSchemeKeyTokens.OnSurface
     val UncheckedIconColor = ColorSchemeKeyTokens.Primary
     val UncheckedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
index 6bb6e21..6ece9c7 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
@@ -42,6 +42,7 @@
 import androidx.wear.compose.foundation.ExpandableState
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealState
 import androidx.wear.compose.foundation.RevealValue
 import androidx.wear.compose.foundation.SwipeToDismissBoxState
 import androidx.wear.compose.foundation.edgeSwipeToDismiss
@@ -67,7 +68,7 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
+@OptIn(ExperimentalWearFoundationApi::class)
 @Composable
 fun SwipeToRevealChips(swipeToDismissBoxState: SwipeToDismissBoxState) {
     val expandableStateMapping = rememberExpandableStateMapping<Int>(
@@ -90,7 +91,7 @@
                 var undoActionEnabled by remember { mutableStateOf(true) }
                 val revealState = rememberRevealState()
                 val coroutineScope = rememberCoroutineScope()
-                val deleteItem = {
+                val deleteItem: () -> Unit = {
                     coroutineScope.launch {
                         revealState.animateTo(RevealValue.Revealed)
 
@@ -103,7 +104,7 @@
                         }
                     }
                 }
-                val addItem = {
+                val addItem: () -> Unit = {
                     coroutineScope.launch {
                         revealState.animateTo(RevealValue.Revealed)
                         itemCount++
@@ -116,85 +117,33 @@
                         }
                     }
                 }
-                if (expanded) {
-                    SwipeToRevealChip(
-                        modifier = Modifier
-                            .edgeSwipeToDismiss(swipeToDismissBoxState)
-                            .semantics {
-                                customActions = listOf(
-                                    CustomAccessibilityAction("Delete") {
-                                        deleteItem()
-                                        true
-                                    },
-                                    CustomAccessibilityAction("Duplicate") {
-                                        addItem()
-                                        true
-                                    }
-                                )
-                            },
-                        revealState = revealState,
-                         deleteItem() },
-                        primaryAction = {
-                            SwipeToRevealPrimaryAction(
-                                revealState = revealState,
-                                icon = {
-                                    Icon(
-                                        SwipeToRevealDefaults.Delete,
-                                        contentDescription = "Delete"
-                                    )
-                                },
-                                label = { Text(text = "Delete") },
-                                 deleteItem() },
-                            )
-                        },
-                        secondaryAction = {
-                            SwipeToRevealSecondaryAction(
-                                revealState = revealState,
-                                content = {
-                                    Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
-                                },
-                                 addItem() }
-                            )
-                        },
-                        undoPrimaryAction = {
-                            SwipeToRevealUndoAction(
-                                revealState = revealState,
-                                label = { Text("Undo Primary Action") },
-                                >
-                                    if (undoActionEnabled) {
-                                        coroutineScope.launch {
-                                            // reset the state when undo is clicked
-                                            revealState.animateTo(RevealValue.Covered)
-                                            revealState.lastActionType = RevealActionType.None
-                                        }
-                                    }
-                                }
-                            )
-                        },
-                        undoSecondaryAction = {
-                            SwipeToRevealUndoAction(
-                                revealState = revealState,
-                                label = { Text("Undo Secondary Action") },
-                                >
-                                    coroutineScope.launch {
-                                        itemCount--
-                                        // reset the state when undo is clicked
-                                        revealState.animateTo(RevealValue.Covered)
-                                        revealState.lastActionType = RevealActionType.None
-                                    }
-                                }
-                            )
+                val undoDeleteItem: () -> Unit = {
+                    if (undoActionEnabled) {
+                        coroutineScope.launch {
+                            // reset the state when undo is clicked
+                            revealState.animateTo(RevealValue.Covered)
+                            revealState.lastActionType = RevealActionType.None
                         }
-                    ) {
-                        Chip(
-                             /*TODO*/ },
-                            colors = ChipDefaults.secondaryChipColors(),
-                            modifier = Modifier.fillMaxWidth(),
-                            label = {
-                                Text("Chip #$it")
-                            }
-                        )
                     }
+                }
+                val undoAddItem: () -> Unit = {
+                    coroutineScope.launch {
+                        itemCount--
+                        // reset the state when undo is clicked
+                        revealState.animateTo(RevealValue.Covered)
+                        revealState.lastActionType = RevealActionType.None
+                    }
+                }
+                if (expanded) {
+                    SwipeToRevealChipExpandable(
+                        modifier = Modifier.edgeSwipeToDismiss(swipeToDismissBoxState),
+                        text = "Chip #$it",
+                        revealState = revealState,
+                        >
+                        >
+                        >
+                        >
+                    )
                 } else {
                     Spacer(modifier = Modifier.width(200.dp))
                 }
@@ -203,6 +152,86 @@
     }
 }
 
+@OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
+@Composable
+private fun SwipeToRevealChipExpandable(
+    modifier: Modifier = Modifier,
+    text: String,
+    revealState: RevealState,
+    onDeleteAction: () -> Unit,
+    onUndoDelete: () -> Unit,
+    onDuplicateAction: (() -> Unit)?,
+    onUndoDuplicate: (() -> Unit)?
+) {
+    SwipeToRevealChip(
+        modifier = modifier.semantics {
+                customActions = listOfNotNull(
+                    CustomAccessibilityAction("Delete") {
+                        onDeleteAction()
+                        true
+                    },
+                    onDuplicateAction?.let {
+                        CustomAccessibilityAction("Duplicate") {
+                            onDuplicateAction()
+                            true
+                        }
+                    }
+                )
+            },
+        revealState = revealState,
+        >
+        primaryAction = {
+            SwipeToRevealPrimaryAction(
+                revealState = revealState,
+                icon = {
+                    Icon(
+                        SwipeToRevealDefaults.Delete,
+                        contentDescription = "Delete"
+                    )
+                },
+                label = { Text(text = "Delete") },
+                >
+            )
+        },
+        secondaryAction = onDuplicateAction?.let {
+            {
+                SwipeToRevealSecondaryAction(
+                    revealState = revealState,
+                    content = {
+                        Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
+                    },
+                    >
+                )
+            }
+        },
+        undoPrimaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text("Undo Delete") },
+                >
+            )
+        },
+        undoSecondaryAction = onUndoDuplicate?.let {
+            {
+                SwipeToRevealUndoAction(
+                    revealState = revealState,
+                    label = { Text("Undo Duplicate") },
+                    >
+                )
+            }
+        }
+    ) {
+        Chip(
+             /*TODO*/ },
+            colors = ChipDefaults.secondaryChipColors(),
+            modifier = Modifier.fillMaxWidth(),
+            label = {
+                Text(text)
+            }
+        )
+    }
+}
+
 @Composable
 fun SwipeToRevealCards(swipeToDismissBoxState: SwipeToDismissBoxState) {
     val emailMap = mutableMapOf(
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/Fingerprint.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/Fingerprint.java
index d92eaa5..9693204 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/Fingerprint.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/Fingerprint.java
@@ -40,10 +40,34 @@
  */
 @RestrictTo(Scope.LIBRARY_GROUP)
 public final class Fingerprint {
+    private static final int[] POW_31 = {
+        1,
+        31,
+        961,
+        29791,
+        923521,
+        28629151,
+        887503681,
+        1742810335,
+        -1807454463,
+        -196513505,
+        -1796951359,
+        129082719,
+        -293403007,
+        -505558625,
+        1507551809,
+        -510534177,
+        1353309697,
+        -997072353,
+        -844471871,
+        -408824225
+    };
     private static final int DEFAULT_VALUE = 0;
     private final int selfTypeValue;
     private int selfPropsValue;
     private int childNodesValue;
+    // itself + children + grand children + grand grand children + ...
+    private int subNodeCount;
     private @Nullable List<Fingerprint> childNodes;
 
     public Fingerprint(int selfTypeValue) {
@@ -51,6 +75,7 @@
         this.selfPropsValue = DEFAULT_VALUE;
         this.childNodesValue = DEFAULT_VALUE;
         this.childNodes = null;
+        this.subNodeCount = 1; // self
     }
 
     public Fingerprint(@NonNull NodeFingerprint proto) {
@@ -102,7 +127,12 @@
             childNodes = new ArrayList<>();
         }
         childNodes.add(childNode);
-        childNodesValue = (31 * childNodesValue) + childNode.aggregateValueAsInt();
+        // We need to include the number of grandchildren of the new node, otherwise swapping the
+        // place of "b" and "c" in a layout like "[a b] [c d]" could result in the same fingerprint.
+        int coeff = pow31Unsafe(childNode.subNodeCount);
+        childNodesValue =
+                (coeff * childNodesValue) + (childNodes.size() * childNode.aggregateValueAsInt());
+        subNodeCount += childNode.subNodeCount;
     }
 
     /** Record a property value being updated. */
@@ -115,6 +145,25 @@
         selfPropsValue = (31 * selfPropsValue) + entry;
     }
 
+    /**
+     * An int version of {@link Math#pow(double, double)} }. The result will overflow if it's larger
+     * than {@link Integer#MAX_VALUE}.
+     *
+     * <p>Note that this only support positive exponents
+     */
+    private static int pow31Unsafe(int n) {
+        // Check for available precomputed result (as an optimization).
+        if (n < POW_31.length) {
+            return POW_31[n];
+        }
+
+        int result = POW_31[POW_31.length - 1];
+        for (int i = POW_31.length; i <= n; i++) {
+            result *= 31;
+        }
+        return result;
+    }
+
     NodeFingerprint toProto() {
         NodeFingerprint.Builder builder = NodeFingerprint.newBuilder();
         if (selfTypeValue() != 0) {
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/FingerprintTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/FingerprintTest.java
index 2f99ad8..a6eda1c 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/FingerprintTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/FingerprintTest.java
@@ -28,9 +28,13 @@
 public final class FingerprintTest {
     private static final int SELF_TYPE_VALUE = 1234;
     private static final int FIELD_1 = 1;
-    private static final int VALUE_HASH1 = 10;
-
-    private static final int DISCARDED_VALUE = -1;
+    private static final int VALUE_HASH1 = 101;
+    private static final int FIELD_2 = 2;
+    private static final int VALUE_HASH2 = 202;
+    private static final int FIELD_3 = 3;
+    private static final int VALUE_HASH3 = 301;
+    private static final int FIELD_4 = 4;
+    private static final int VALUE_HASH4 = 401;
 
     @Test
     public void addChildNode() {
@@ -59,4 +63,33 @@
         assertThat(child.selfTypeValue()).isEqualTo(SELF_TYPE_VALUE);
         assertThat(child.selfPropsValue()).isEqualTo(31 * FIELD_1 + VALUE_HASH1);
     }
+
+    @Test
+    public void childNodeOrderMatters() {
+        Fingerprint root1 = new Fingerprint(SELF_TYPE_VALUE);
+        Fingerprint root2 = new Fingerprint(SELF_TYPE_VALUE);
+        Fingerprint parent12 = createParentFor(FIELD_1, VALUE_HASH1, FIELD_2, VALUE_HASH2);
+        Fingerprint parent34 = createParentFor(FIELD_3, VALUE_HASH3, FIELD_4, VALUE_HASH4);
+        Fingerprint parent13 = createParentFor(FIELD_1, VALUE_HASH1, FIELD_3, VALUE_HASH3);
+        Fingerprint parent24 = createParentFor(FIELD_2, VALUE_HASH2, FIELD_4, VALUE_HASH4);
+
+        root1.addChildNode(parent12);
+        root1.addChildNode(parent34);
+        root2.addChildNode(parent13);
+        root2.addChildNode(parent24);
+
+        assertThat(root1.childNodesValue()).isNotEqualTo(root2.childNodesValue());
+    }
+
+    private Fingerprint createParentFor(
+            int fieldId1, int fieldValue1, int fieldId2, int fieldValue2) {
+        Fingerprint parentFingerPrint = new Fingerprint(SELF_TYPE_VALUE);
+        Fingerprint child1FingerPrint = new Fingerprint(SELF_TYPE_VALUE);
+        child1FingerPrint.recordPropertyUpdate(fieldId1, fieldValue1);
+        parentFingerPrint.addChildNode(child1FingerPrint);
+        Fingerprint child2FingerPrint = new Fingerprint(SELF_TYPE_VALUE);
+        child2FingerPrint.recordPropertyUpdate(fieldId2, fieldValue2);
+        parentFingerPrint.addChildNode(child2FingerPrint);
+        return parentFingerPrint;
+    }
 }
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/dynamic.proto b/wear/protolayout/protolayout-proto/src/main/proto/dynamic.proto
index f0a0f04..0b7b008 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/dynamic.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/dynamic.proto
@@ -634,7 +634,7 @@
   string zone_id = 2;
 }
 
-// The datetime part to retrieve using ZonedDateTimePartOp.
+// The date-time part to retrieve using ZonedDateTimePartOp.
 enum ZonedDateTimePartType {
   // Undefined date-time part type.
   ZONED_DATE_TIME_PART_UNDEFINED = 0;
@@ -654,12 +654,12 @@
   ZONED_DATE_TIME_PART_YEAR = 7;
 }
 
-// Retrieve the specified datetime part of a DynamicZonedDateTime instance as a
+// Retrieve the specified date-time part of a DynamicZonedDateTime instance as a
 // DynamicInt32.
 message GetZonedDateTimePartOp {
-  // The zoned datetime input.
+  // The zoned date-time input.
   DynamicZonedDateTime input = 1;
-  // The datetime part to retrieve.
+  // The date-time part to retrieve.
   ZonedDateTimePartType part_type = 2;
 }
 
@@ -755,7 +755,6 @@
   DurationPartType duration_part = 2;
 }
 
-
 // A dynamic Instant which sources its data from the a state entry.
 message StateInstantSource {
   // The key in the state to bind to.
@@ -772,4 +771,4 @@
 
   // The namespace for the state key.
   string source_namespace = 2;
-}
\ No newline at end of file
+}
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
index 704f5fd..821d610 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
@@ -526,6 +526,7 @@
     testContext: Context,
     private var surfaceHolderOverride: SurfaceHolder
 ) : WatchFaceService() {
+    var lastComplicationType: ComplicationType? = null
 
     init {
         attachBaseContext(testContext)
@@ -605,7 +606,10 @@
                     CanvasType.HARDWARE,
                     16
                 ) {
-                override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {}
+                override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+                    lastComplicationType =
+                        complicationSlotsManager[123]!!.complicationData.value.type
+                }
 
                 override fun renderHighlightLayer(
                     canvas: Canvas,
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 2afa5d96..b77902c 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -1344,6 +1344,73 @@
 
         assertTrue(ObservableServiceC.awaitForServiceToBeBound(UPDATE_TIMEOUT_MILLIS))
     }
+
+    @Test
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
+    fun overrideComplicationData() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
+            return
+        }
+        val wallpaperService =
+            TestComplicationProviderDefaultsWatchFaceService(context, surfaceHolder)
+        val interactiveInstance = getOrCreateTestSubject(wallpaperService)
+        interactiveInstance.updateComplicationData(
+            mapOf(123 to rangedValueComplicationBuilder().build())
+        )
+
+        interactiveInstance.overrideComplicationData(
+            mapOf(
+                123 to
+                    ShortTextComplicationData.Builder(
+                        PlainComplicationText.Builder("TEST").build(),
+                        ComplicationText.EMPTY
+                    )
+                        .build()
+            )
+        )
+
+        interactiveInstance.renderWatchFaceToBitmap(
+            RenderParameters(DrawMode.INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null),
+            Instant.ofEpochMilli(1234567),
+            null,
+            null
+        )
+        assertThat(wallpaperService.lastComplicationType).isEqualTo(ComplicationType.SHORT_TEXT)
+    }
+
+    @Test
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
+    fun clearComplicationDataOverride() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
+            return
+        }
+        val wallpaperService =
+            TestComplicationProviderDefaultsWatchFaceService(context, surfaceHolder)
+        val interactiveInstance = getOrCreateTestSubject(wallpaperService)
+        interactiveInstance.updateComplicationData(
+            mapOf(123 to rangedValueComplicationBuilder().build())
+        )
+        interactiveInstance.overrideComplicationData(
+            mapOf(
+                123 to
+                    ShortTextComplicationData.Builder(
+                        PlainComplicationText.Builder("TEST").build(),
+                        ComplicationText.EMPTY
+                    )
+                        .build(),
+            )
+        )
+
+        interactiveInstance.clearComplicationDataOverride()
+
+        interactiveInstance.renderWatchFaceToBitmap(
+            RenderParameters(DrawMode.INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null),
+            Instant.ofEpochMilli(1234567),
+            null,
+            null
+        )
+        assertThat(wallpaperService.lastComplicationType).isEqualTo(ComplicationType.RANGED_VALUE)
+    }
 }
 
 @RunWith(AndroidJUnit4::class)
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index 6112bfe..44bd7b7 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -141,6 +141,23 @@
     public fun updateComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>)
 
     /**
+     * Sets override complications which are displayed until [clearComplicationDataOverride] is
+     * called. For editors this is more efficient than repeatedly calling [renderWatchFaceToBitmap]
+     * with complication data.
+     *
+     * While there are overrides [updateComplicationData] has no effect until
+     * [clearComplicationDataOverride] is called.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun overrideComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>) {}
+
+    /**
+     * Clears any overrides set by [overrideComplicationData].
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun clearComplicationDataOverride() {}
+
+    /**
      * Renders the watchface to a shared memory backed [Bitmap] with the given settings. Note this
      * will be fairly slow since either software canvas or glReadPixels will be invoked.
      *
@@ -433,6 +450,7 @@
     private var lastWatchFaceColors: WatchFaceColors? = null
     private var disconnectReason: Int? = null
     private var closed = false
+    private var overrideSlotIdToComplicationData = HashMap<Int, ComplicationData>()
 
     private val iWatchFaceListener =
         object : IWatchfaceListener.Stub() {
@@ -505,6 +523,31 @@
             )
         }
 
+    override fun overrideComplicationData(slotIdToComplicationData: Map<Int, ComplicationData>) {
+        if (iInteractiveWatchFace.apiVersion >= 11) {
+            iInteractiveWatchFace.overrideComplicationData(
+                slotIdToComplicationData.map {
+                    IdAndComplicationDataWireFormat(
+                        it.key,
+                        it.value.asWireComplicationData()
+                    )
+                }
+            )
+        } else {
+            for ((id, complicationData) in slotIdToComplicationData) {
+                overrideSlotIdToComplicationData[id] = complicationData
+            }
+        }
+    }
+
+    override fun clearComplicationDataOverride() {
+        if (iInteractiveWatchFace.apiVersion >= 11) {
+            iInteractiveWatchFace.clearComplicationDataOverride()
+        } else {
+            overrideSlotIdToComplicationData.clear()
+        }
+    }
+
     @RequiresApi(27)
     override fun renderWatchFaceToBitmap(
         renderParameters: RenderParameters,
@@ -519,7 +562,11 @@
                         renderParameters.toWireFormat(),
                         instant.toEpochMilli(),
                         userStyle?.toWireFormat(),
-                        idAndComplicationData?.map {
+                        if (iInteractiveWatchFace.apiVersion >= 11) {
+                            idAndComplicationData
+                        } else {
+                            mergeWithOverrideComplicationData(idAndComplicationData)
+                        }?.map {
                             IdAndComplicationDataWireFormat(
                                 it.key,
                                 it.value.asWireComplicationData()
@@ -530,6 +577,27 @@
             )
         }
 
+    private fun mergeWithOverrideComplicationData(
+        idAndComplicationData: Map<Int, ComplicationData>?
+    ): Map<Int, ComplicationData>? {
+        if (overrideSlotIdToComplicationData.isEmpty()) {
+            return idAndComplicationData
+        }
+
+        if (idAndComplicationData.isNullOrEmpty()) {
+            return overrideSlotIdToComplicationData
+        }
+
+        val merged = HashMap(overrideSlotIdToComplicationData)
+        for ((id, complicationData) in idAndComplicationData) {
+            if (merged.contains(id)) {
+                continue
+            }
+            merged[id] = complicationData
+        }
+        return merged
+    }
+
     override val isRemoteWatchFaceViewHostSupported = iInteractiveWatchFace.apiVersion >= 9
 
     @RequiresApi(Build.VERSION_CODES.R)
diff --git a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
index c06fdab8..7e61727 100644
--- a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
+++ b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IInteractiveWatchFace.aidl
@@ -37,12 +37,12 @@
 interface IInteractiveWatchFace {
     // IMPORTANT NOTE: All methods must be given an explicit transaction id that must never change
     // in the future to remain binary backwards compatible.
-    // Next Id: 25
+    // Next Id: 28
 
     /**
      * API version number. This should be incremented every time a new method is added.
      */
-    const int API_VERSION = 10;
+    const int API_VERSION = 11;
 
     /** Indicates a "down" touch event on the watch face. */
     const int TAP_TYPE_DOWN = 0;
@@ -243,4 +243,22 @@
      * @since API version 10.
      */
     UserStyleFlavorsWireFormat getUserStyleFlavors() = 25;
+
+    /**
+     * Send override ComplicationData to be used until clearComplicationDataOverride is called.
+     * While overrides, any calls to updateComplicationData are deferred until
+     * clearComplicationDataOverride is called.
+     *
+     * @since API version 11.
+     */
+    oneway void overrideComplicationData(
+        in List<IdAndComplicationDataWireFormat> complicationData) = 26;
+
+    /**
+     * Clears any complicaton data set by overrideComplicationData, and activates any complications
+     * set by updateComplicationData.
+     *
+     * @since API version 11.
+     */
+    oneway void clearComplicationDataOverride() = 27;
 }
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index 2f02cf3..aec9b5c 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -92,10 +92,7 @@
         object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
                 when (intent.action) {
-                    Intent.ACTION_BATTERY_LOW -> observer.onActionBatteryLow()
-                    Intent.ACTION_BATTERY_OKAY -> observer.onActionBatteryOkay()
-                    Intent.ACTION_POWER_CONNECTED -> observer.onActionPowerConnected()
-                    Intent.ACTION_POWER_DISCONNECTED -> observer.onActionPowerDisconnected()
+                    Intent.ACTION_BATTERY_CHANGED -> processBatteryStatus(intent)
                     Intent.ACTION_TIME_CHANGED -> observer.onActionTimeChanged()
                     Intent.ACTION_TIME_TICK -> observer.onActionTimeTick()
                     Intent.ACTION_TIMEZONE_CHANGED -> observer.onActionTimeZoneChanged()
@@ -112,15 +109,11 @@
     init {
         context.registerReceiver(
             receiver,
-            IntentFilter(Intent.ACTION_SCREEN_OFF).apply {
-                addAction(Intent.ACTION_SCREEN_ON)
+            IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
+                addAction(Intent.ACTION_TIME_CHANGED)
                 addAction(Intent.ACTION_TIME_TICK)
                 addAction(Intent.ACTION_TIMEZONE_CHANGED)
-                addAction(Intent.ACTION_TIME_CHANGED)
-                addAction(Intent.ACTION_BATTERY_LOW)
-                addAction(Intent.ACTION_BATTERY_OKAY)
-                addAction(Intent.ACTION_POWER_CONNECTED)
-                addAction(Intent.ACTION_POWER_DISCONNECTED)
+                addAction(Intent.ACTION_SCREEN_OFF)
                 addAction(Intent.ACTION_USER_PRESENT)
                 addAction(WatchFaceImpl.MOCK_TIME_INTENT)
                 addAction(ACTION_AMBIENT_STARTED)
@@ -137,7 +130,6 @@
         )
     }
 
-    /** Called to send observers initial battery state in advance of receiving any broadcasts. */
     internal fun processBatteryStatus(batteryStatus: Intent?) {
         val status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
         if (
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index 3c7ec5c..7cd7f68 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi
 import androidx.wear.watchface.TapEvent
 import androidx.wear.watchface.WatchFaceService
+import androidx.wear.watchface.complications.data.toApiComplicationData
 import androidx.wear.watchface.control.data.WatchFaceRenderParams
 import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
 import androidx.wear.watchface.data.IdAndComplicationStateWireFormat
@@ -292,6 +293,22 @@
             }
         }
 
+    override fun overrideComplicationData(
+        complicationDatumWireFormats: List<IdAndComplicationDataWireFormat>
+    ): Unit = aidlMethod(TAG, "overrideComplicationData") {
+        engine?.overrideComplications(
+            complicationDatumWireFormats.associateBy(
+                { it.id },
+                { it.complicationData.toApiComplicationData() }
+            )
+        )
+    }
+
+    override fun clearComplicationDataOverride(): Unit =
+        aidlMethod(TAG, "overrideComplicationData") {
+            engine?.removeAnyComplicationOverrides()
+        }
+
     fun onDestroy() {
         // Note this is almost certainly called on the ui thread, from release() above.
         runBlocking {
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index cf39775..470a7e1 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -1426,14 +1426,35 @@
         // The delay should change when battery is low.
         watchFaceImpl.broadcastsReceiver!!
             .receiver
-            .onReceive(context, Intent(Intent.ACTION_BATTERY_LOW))
+            .onReceive(
+                context,
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
+                    putExtra(
+                        BatteryManager.EXTRA_LEVEL,
+                        (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+                    )
+                    putExtra(BatteryManager.EXTRA_SCALE, 100)
+                }
+            )
+
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
             .isEqualTo(WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS)
 
         // And go back to normal when battery is OK.
         watchFaceImpl.broadcastsReceiver!!
             .receiver
-            .onReceive(context, Intent(Intent.ACTION_BATTERY_OKAY))
+            .onReceive(
+                context,
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
+                    putExtra(
+                        BatteryManager.EXTRA_LEVEL,
+                        (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD + 1).toInt()
+                    )
+                    putExtra(BatteryManager.EXTRA_SCALE, 100)
+                }
+            )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
             .isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
     }
@@ -1453,14 +1474,34 @@
         // The delay should change when battery is low.
         watchFaceImpl.broadcastsReceiver!!
             .receiver
-            .onReceive(context, Intent(Intent.ACTION_BATTERY_LOW))
+            .onReceive(
+                context,
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
+                    putExtra(
+                        BatteryManager.EXTRA_LEVEL,
+                        (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+                    )
+                    putExtra(BatteryManager.EXTRA_SCALE, 100)
+                }
+            )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
             .isEqualTo(WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS)
 
         // And go back to normal when power is connected.
         watchFaceImpl.broadcastsReceiver!!
             .receiver
-            .onReceive(context, Intent(Intent.ACTION_POWER_CONNECTED))
+            .onReceive(
+                context,
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
+                    putExtra(
+                        BatteryManager.EXTRA_LEVEL,
+                        (BroadcastsReceiver.INITIAL_LOW_BATTERY_THRESHOLD - 1).toInt()
+                    )
+                    putExtra(BatteryManager.EXTRA_SCALE, 100)
+                }
+            )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0, Instant.EPOCH))
             .isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
     }