Merge "Ruler API Feedback" into androidx-main
diff --git a/autofill/autofill/src/main/java/androidx/autofill/DeleteMe.kt b/autofill/autofill/src/main/java/androidx/autofill/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/autofill/autofill/src/main/java/androidx/autofill/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
index 1e43fc2..0025f4c 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
@@ -23,8 +23,8 @@
 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX
 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BENCHMARK_PREFIX
 import androidx.baselineprofile.gradle.utils.Dependencies
-import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
-import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED_INCLUSIVE
 import androidx.baselineprofile.gradle.utils.camelCase
 import androidx.baselineprofile.gradle.utils.copyBuildTypeSources
 import androidx.baselineprofile.gradle.utils.createExtendedBuildTypes
@@ -53,8 +53,8 @@
         AgpPluginId.ID_ANDROID_APPLICATION_PLUGIN,
         AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN
     ),
-    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
-    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+    minAgpVersionInclusive = MIN_AGP_VERSION_REQUIRED_INCLUSIVE,
+    maxAgpVersionExclusive = MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
 ) {
 
     private val ApplicationExtension.debugSigningConfig
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
index 0f3480d..217841b 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
@@ -31,8 +31,8 @@
 import androidx.baselineprofile.gradle.utils.INTERMEDIATES_BASE_FOLDER
 import androidx.baselineprofile.gradle.utils.KOTLIN_MULTIPLATFORM_PLUGIN_ID
 import androidx.baselineprofile.gradle.utils.KotlinMultiPlatformUtils
-import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
-import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED_INCLUSIVE
 import androidx.baselineprofile.gradle.utils.R8Utils
 import androidx.baselineprofile.gradle.utils.RELEASE
 import androidx.baselineprofile.gradle.utils.camelCase
@@ -64,8 +64,8 @@
         AgpPluginId.ID_ANDROID_APPLICATION_PLUGIN,
         AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN
     ),
-    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
-    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+    minAgpVersionInclusive = MIN_AGP_VERSION_REQUIRED_INCLUSIVE,
+    maxAgpVersionExclusive = MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
 ) {
 
     // List of the non debuggable build types
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
index a7ef11b..33347c5 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
@@ -34,8 +34,8 @@
 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_ENABLED_RULES_BENCHMARK
 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_TARGET_PACKAGE_NAME
 import androidx.baselineprofile.gradle.utils.InstrumentationTestRunnerArgumentsAgp82
-import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
-import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED_INCLUSIVE
 import androidx.baselineprofile.gradle.utils.RELEASE
 import androidx.baselineprofile.gradle.utils.TestedApksAgp83
 import androidx.baselineprofile.gradle.utils.camelCase
@@ -64,8 +64,8 @@
 private class BaselineProfileProducerAgpPlugin(private val project: Project) : AgpPlugin(
     project = project,
     supportedAgpPlugins = setOf(AgpPluginId.ID_ANDROID_TEST_PLUGIN),
-    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
-    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+    minAgpVersionInclusive = MIN_AGP_VERSION_REQUIRED_INCLUSIVE,
+    maxAgpVersionExclusive = MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
 ) {
 
     companion object {
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt
index ea70bdf..ae1c025 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt
@@ -46,8 +46,8 @@
 internal abstract class AgpPlugin(
     private val project: Project,
     private val supportedAgpPlugins: Set<AgpPluginId>,
-    private val minAgpVersion: AndroidPluginVersion,
-    private val maxAgpVersion: AndroidPluginVersion,
+    private val minAgpVersionInclusive: AndroidPluginVersion,
+    private val maxAgpVersionExclusive: AndroidPluginVersion,
 ) {
 
     protected val logger: Logger
@@ -93,7 +93,7 @@
 
     private fun configureWithAndroidPlugin() {
 
-        checkAgpVersion(min = minAgpVersion, max = maxAgpVersion)
+        checkAgpVersion()
 
         onBeforeFinalizeDsl()
 
@@ -224,22 +224,22 @@
 
     protected fun agpVersion() = project.agpVersion()
 
-    private fun checkAgpVersion(min: AndroidPluginVersion, max: AndroidPluginVersion) {
+    private fun checkAgpVersion() {
         val agpVersion = project.agpVersion()
-        if (agpVersion < min) {
+        if (agpVersion < minAgpVersionInclusive) {
             throw GradleException(
                 """
         This version of the Baseline Profile Gradle Plugin requires the Android Gradle Plugin to be
-        at least version $MIN_AGP_VERSION_REQUIRED. The current version is $agpVersion.
+        at least version $minAgpVersionInclusive. The current version is $agpVersion.
         Please update your project.
             """.trimIndent()
             )
         }
-        if (agpVersion > max) {
+        if (agpVersion >= maxAgpVersionExclusive) {
             logger.warn(
                 """
-        This version of the Baseline Profile Gradle Plugin was tested at most with the Android
-        Gradle Plugin version $MAX_AGP_VERSION_REQUIRED and it may not work as intended.
+        This version of the Baseline Profile Gradle Plugin was tested with versions below Android
+        Gradle Plugin version $maxAgpVersionExclusive and it may not work as intended.
         Current version is $agpVersion.
                 """.trimIndent()
             )
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
index ebe0a317..c82ead6 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
@@ -19,8 +19,8 @@
 import com.android.build.api.AndroidPluginVersion
 
 // Minimum AGP version required
-internal val MIN_AGP_VERSION_REQUIRED = AndroidPluginVersion(8, 0, 0)
-internal val MAX_AGP_VERSION_REQUIRED = AndroidPluginVersion(8, 4, 0)
+internal val MIN_AGP_VERSION_REQUIRED_INCLUSIVE = AndroidPluginVersion(8, 0, 0)
+internal val MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE = AndroidPluginVersion(8, 5, 0).alpha(1)
 
 // Prefix for the build type baseline profile
 internal const val BUILD_TYPE_BASELINE_PROFILE_PREFIX = "nonMinified"
diff --git a/biometric/biometric/src/main/res/values-fa/strings.xml b/biometric/biometric/src/main/res/values-fa/strings.xml
index ac22959..6e9482f 100644
--- a/biometric/biometric/src/main/res/values-fa/strings.xml
+++ b/biometric/biometric/src/main/res/values-fa/strings.xml
@@ -32,11 +32,11 @@
     <string name="fingerprint_dialog_icon_description" msgid="5462024216548165325">"نماد اثر انگشت"</string>
     <string name="use_fingerprint_label" msgid="6961788485681412417">"استفاده از اثر انگشت"</string>
     <string name="use_face_label" msgid="6533512708069459542">"استفاده از چهره"</string>
-    <string name="use_biometric_label" msgid="6524145989441579428">"استفاده از زیست‌سنجشی"</string>
+    <string name="use_biometric_label" msgid="6524145989441579428">"استفاده از داده‌های زیست‌سنجشی"</string>
     <string name="use_screen_lock_label" msgid="5459869335976243512">"از قفل صفحه استفاده کنید"</string>
     <string name="use_fingerprint_or_screen_lock_label" msgid="7577690399303139443">"استفاده از اثر انگشت یا قفل صفحه"</string>
     <string name="use_face_or_screen_lock_label" msgid="2116180187159450292">"استفاده از قفل صفحه یا چهره"</string>
-    <string name="use_biometric_or_screen_lock_label" msgid="5385448280139639016">"استفاده از زیست‌سنجشی یا قفل صفحه"</string>
+    <string name="use_biometric_or_screen_lock_label" msgid="5385448280139639016">"استفاده از داده‌های زیست‌سنجشی یا قفل صفحه"</string>
     <string name="fingerprint_prompt_message" msgid="7449360011861769080">"برای ادامه، از اثر انگشتتان استفاده کنید"</string>
     <string name="face_prompt_message" msgid="2282389249605674226">"برای ادامه، از چهره‌تان استفاده کنید"</string>
     <string name="biometric_prompt_message" msgid="1160635338192065472">"برای ادامه، از زیست‌سنجشی استفاده کنید"</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index ea50cd9..6e64208 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -45,7 +45,7 @@
 import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnJvmCompilation
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
-import com.android.build.api.variant.HasAndroidTest
+import com.android.build.api.variant.HasDeviceTests
 import com.android.build.api.variant.HasUnitTestBuilder
 import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
 import com.android.build.api.variant.LibraryAndroidComponentsExtension
@@ -688,34 +688,34 @@
         }
     }
 
-    private fun HasAndroidTest.configureTests() {
-        configureLicensePackaging()
-        androidTest?.packaging?.resources?.apply { excludeVersionFiles(this) }
-    }
+    @Suppress("UnstableApiUsage") // usage of HasDeviceTests
+    private fun HasDeviceTests.configureTests() {
+        deviceTests.forEach { deviceTest ->
+            deviceTest.packaging.resources.apply {
+                excludeVersionFiles(this)
 
-    @Suppress("UnstableApiUsage") // usage of experimentalProperties
-    private fun Variant.artRewritingWorkaround() {
-        // b/279234807
-        experimentalProperties.put("android.experimental.art-profile-r8-rewriting", false)
-    }
-
-    @Suppress("UnstableApiUsage") // usage of experimentalProperties
-    private fun Variant.aotCompileMicrobenchmarks(project: Project) {
-        if (project.hasBenchmarkPlugin()) {
-            experimentalProperties.put("android.experimental.force-aot-compilation", true)
+                // Workaround a limitation in AGP that fails to merge these META-INF license files.
+                pickFirsts.add("/META-INF/AL2.0")
+                // In addition to working around the above issue, we exclude the LGPL2.1 license as
+                // we're
+                // approved to distribute code via AL2.0 and the only dependencies which pull in LGPL2.1
+                // are currently dual-licensed with AL2.0 and LGPL2.1. The affected dependencies are:
+                //   - net.java.dev.jna:jna:5.5.0
+                excludes.add("/META-INF/LGPL2.1")
+            }
         }
     }
 
-    private fun HasAndroidTest.configureLicensePackaging() {
-        androidTest?.packaging?.resources?.apply {
-            // Workaround a limitation in AGP that fails to merge these META-INF license files.
-            pickFirsts.add("/META-INF/AL2.0")
-            // In addition to working around the above issue, we exclude the LGPL2.1 license as
-            // we're
-            // approved to distribute code via AL2.0 and the only dependencies which pull in LGPL2.1
-            // are currently dual-licensed with AL2.0 and LGPL2.1. The affected dependencies are:
-            //   - net.java.dev.jna:jna:5.5.0
-            excludes.add("/META-INF/LGPL2.1")
+    private fun Variant.artRewritingWorkaround() {
+        // b/279234807
+        @Suppress("UnstableApiUsage") // usage of experimentalProperties
+        experimentalProperties.put("android.experimental.art-profile-r8-rewriting", false)
+    }
+
+    private fun Variant.aotCompileMicrobenchmarks(project: Project) {
+        if (project.hasBenchmarkPlugin()) {
+            @Suppress("UnstableApiUsage") // usage of experimentalProperties
+            experimentalProperties.put("android.experimental.force-aot-compilation", true)
         }
     }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
index ca5e6cb..53b5d51 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
@@ -21,7 +21,7 @@
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
 import com.android.build.api.variant.BuiltArtifactsLoader
-import com.android.build.api.variant.HasAndroidTest
+import com.android.build.api.variant.HasDeviceTests
 import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
 import javax.inject.Inject
 import org.gradle.api.DefaultTask
@@ -200,33 +200,33 @@
         "ftlCoreTelecomDeviceSet" to listOf(NEXUS_6P, A10, PETTYL, HWCOR, Q2Q),
     )
 
+internal fun Project.registerRunner(
+    name: String,
+    artifacts: Artifacts,
+    namespace: Provider<String>
+) {
+    devicesToRunOn.forEach { (taskPrefix, model) ->
+        tasks.register("$taskPrefix$name", FtlRunner::class.java) { task ->
+            task.device.set(model)
+            task.apkPackageName.set(namespace)
+            task.testFolder.set(artifacts.get(SingleArtifact.APK))
+            task.testLoader.set(artifacts.getBuiltArtifactsLoader())
+        }
+    }
+}
+
 fun Project.configureFtlRunner(androidComponentsExtension: AndroidComponentsExtension<*, *, *>) {
     androidComponentsExtension.apply {
         onVariants { variant ->
-            var name: String? = null
-            var artifacts: Artifacts? = null
-            var apkPackageName: Provider<String>? = null
+            @Suppress("UnstableApiUsage") // usage of HasDeviceTests
             when {
-                variant is HasAndroidTest -> {
-                    name = variant.androidTest?.name
-                    artifacts = variant.androidTest?.artifacts
-                    apkPackageName = variant.androidTest?.namespace
+                variant is HasDeviceTests -> {
+                    variant.deviceTests.forEach { deviceTest ->
+                        registerRunner(deviceTest.name, deviceTest.artifacts, deviceTest.namespace)
+                    }
                 }
                 project.plugins.hasPlugin("com.android.test") -> {
-                    name = variant.name
-                    artifacts = variant.artifacts
-                    apkPackageName = variant.namespace
-                }
-            }
-            if (name == null || artifacts == null || apkPackageName == null) {
-                return@onVariants
-            }
-            devicesToRunOn.forEach { (taskPrefix, model) ->
-                tasks.register("$taskPrefix$name", FtlRunner::class.java) { task ->
-                    task.device.set(model)
-                    task.apkPackageName.set(apkPackageName)
-                    task.testFolder.set(artifacts.get(SingleArtifact.APK))
-                    task.testLoader.set(artifacts.getBuiltArtifactsLoader())
+                    registerRunner(variant.name, variant.artifacts, variant.namespace)
                 }
             }
         }
@@ -235,16 +235,9 @@
 
 fun Project.configureFtlRunner(componentsExtension: KotlinMultiplatformAndroidComponentsExtension) {
     componentsExtension.onVariant { variant ->
-        val name = variant.androidTest?.name ?: return@onVariant
-        val artifacts = variant.androidTest?.artifacts ?: return@onVariant
-        val apkPackageName = variant.androidTest?.namespace ?: return@onVariant
-        devicesToRunOn.forEach { (taskPrefix, model) ->
-            tasks.register("$taskPrefix$name", FtlRunner::class.java) { task ->
-                task.device.set(model)
-                task.apkPackageName.set(apkPackageName)
-                task.testFolder.set(artifacts.get(SingleArtifact.APK))
-                task.testLoader.set(artifacts.getBuiltArtifactsLoader())
-            }
+        @Suppress("UnstableApiUsage") // usage of HasDeviceTests
+        variant.deviceTests.forEach { deviceTest ->
+            registerRunner(deviceTest.name, deviceTest.artifacts, deviceTest.namespace)
         }
     }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
index 92eb1f6..8d3766c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
@@ -17,7 +17,8 @@
 package androidx.build.clang
 
 import androidx.build.androidExtension
-import com.android.build.api.variant.HasAndroidTest
+import com.android.build.api.variant.HasDeviceTests
+import com.android.build.api.variant.SourceDirectories
 import com.android.utils.appendCapitalized
 import org.gradle.api.Project
 import org.gradle.kotlin.dsl.get
@@ -84,35 +85,43 @@
                 variantBuildType
             )
         ) { variant ->
-            val jniLibsSources = if (forTest) {
-                check(variant is HasAndroidTest) {
-                    "Variant $variant does not have a test target"
+            fun setup(name: String, jniLibsSources: SourceDirectories.Layered?) {
+                checkNotNull(jniLibsSources) {
+                    "Cannot find jni libs sources for variant: " +
+                        "$variant($variantBuildType / $forTest)"
                 }
-                variant.androidTest?.sources?.jniLibs
-            } else {
-                variant.sources.jniLibs
-            }
-            checkNotNull(jniLibsSources) {
-                "Cannot find jni libs sources for variant: $variant($variantBuildType / $forTest)"
-            }
-            val combineTask = project.tasks.register(
-                "createJniLibsDirectoryFor".appendCapitalized(
-                    nativeCompilation.archiveName,
-                    if (forTest) "forTest" else "forMain",
-                    androidTarget.name
-                ),
-                CombineObjectFilesTask::class.java
-            )
-            combineTask.configureFrom(nativeCompilation) {
-                it.family == Family.ANDROID
+                val combineTask = project.tasks.register(
+                    "createJniLibsDirectoryFor".appendCapitalized(
+                        nativeCompilation.archiveName,
+                        "for",
+                        name,
+                        androidTarget.name
+                    ),
+                    CombineObjectFilesTask::class.java
+                )
+                combineTask.configureFrom(nativeCompilation) {
+                    it.family == Family.ANDROID
+                }
+
+                jniLibsSources.addGeneratedSourceDirectory(
+                    taskProvider = combineTask,
+                    wiredWith = {
+                        it.outputDirectory
+                    }
+                )
             }
 
-            jniLibsSources.addGeneratedSourceDirectory(
-                taskProvider = combineTask,
-                wiredWith = {
-                    it.outputDirectory
+            @Suppress("UnstableApiUsage") // usage of HasDeviceTests
+            if (forTest) {
+                check(variant is HasDeviceTests) {
+                    "Variant $variant does not have a test target"
                 }
-            )
+                variant.deviceTests.forEach { deviceTest ->
+                    setup(deviceTest.name, deviceTest.sources.jniLibs)
+                }
+            } else {
+                setup(variant.name, variant.sources.jniLibs)
+            }
         }
     }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index d7417a4..3f18ee4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -111,11 +111,9 @@
 
 fun Project.getMetalavaClasspath(): FileCollection {
     val configuration =
-        configurations.findByName("metalava")
-            ?: configurations.create("metalava") {
-                it.dependencies.add(dependencies.create(getLibraryByName("metalava")))
-                it.isCanBeConsumed = false
-            }
+        configurations.detachedConfiguration(
+                dependencies.create(getLibraryByName("metalava"))
+        )
     return project.files(configuration)
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index d38bad3..2d777b8 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -30,10 +30,9 @@
 import com.android.build.api.artifact.Artifacts
 import com.android.build.api.artifact.SingleArtifact
 import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
-import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnDeviceCompilation
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
-import com.android.build.api.variant.HasAndroidTest
+import com.android.build.api.variant.HasDeviceTests
 import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
 import com.android.build.api.variant.LibraryAndroidComponentsExtension
 import com.android.build.api.variant.TestAndroidComponentsExtension
@@ -64,8 +63,8 @@
     variantName: String,
     artifacts: Artifacts,
     minSdk: Int,
-    testRunner: String,
-    instrumentationRunnerArgs: Map<String, String>
+    testRunner: Provider<String>,
+    instrumentationRunnerArgs: Provider<Map<String, String>>
 ) {
     val xmlName = "${path.asFilenamePrefix()}$variantName.xml"
     val jsonName = "_${path.asFilenamePrefix()}$variantName.json"
@@ -115,12 +114,7 @@
             task.outputJson.set(getFileInTestConfigDirectory(jsonName))
             task.presubmit.set(isPresubmitBuild())
             task.instrumentationArgs.putAll(instrumentationRunnerArgs)
-            // Disable work tests on < API 18: b/178127496
-            if (path.startsWith(":work:")) {
-                task.minSdk.set(maxOf(18, minSdk))
-            } else {
-                task.minSdk.set(minSdk)
-            }
+            task.minSdk.set(minSdk)
             val hasBenchmarkPlugin = hasBenchmarkPlugin()
             task.hasBenchmarkPlugin.set(hasBenchmarkPlugin)
             task.testRunner.set(testRunner)
@@ -172,6 +166,7 @@
         }
     }
 
+    // Migrate away when b/280680434 is fixed.
     // For tests modules, the instrumentation apk is pulled from the <variant>TestedApks
     // configuration. Note that also the associated test configuration task name is different
     // from the application one.
@@ -290,7 +285,7 @@
     variantName: String,
     artifacts: Artifacts,
     minSdk: Int,
-    testRunner: String,
+    testRunner: Provider<String>,
 ) {
     val mediaTask = getOrCreateMediaTestConfigTask(this)
 
@@ -404,40 +399,44 @@
     }
 }
 
+@Suppress("UnstableApiUsage") // usage of HasDeviceTests
 fun Project.configureTestConfigGeneration(baseExtension: BaseExtension) {
     extensions.getByType(AndroidComponentsExtension::class.java).apply {
         onVariants { variant ->
-            var name: String? = null
-            var artifacts: Artifacts? = null
             when {
-                variant is HasAndroidTest -> {
-                    name = variant.androidTest?.name
-                    artifacts = variant.androidTest?.artifacts
+                variant is HasDeviceTests -> {
+                    variant.deviceTests.forEach { deviceTest ->
+                        when {
+                            path.contains("media:version-compat-tests:") -> {
+                                createOrUpdateMediaTestConfigurationGenerationTask(
+                                    deviceTest.name,
+                                    deviceTest.artifacts,
+                                    // replace minSdk after b/328495232 is fixed
+                                    baseExtension.defaultConfig.minSdk!!,
+                                    deviceTest.instrumentationRunner,
+                                )
+                            }
+                            else -> {
+                                createTestConfigurationGenerationTask(
+                                    deviceTest.name,
+                                    deviceTest.artifacts,
+                                    // replace minSdk after b/328495232 is fixed
+                                    baseExtension.defaultConfig.minSdk!!,
+                                    deviceTest.instrumentationRunner,
+                                    deviceTest.instrumentationRunnerArguments
+                                )
+                            }
+                        }
+                    }
                 }
                 project.plugins.hasPlugin("com.android.test") -> {
-                    name = variant.name
-                    artifacts = variant.artifacts
-                }
-            }
-            if (name == null || artifacts == null) {
-                return@onVariants
-            }
-            when {
-                path.contains("media:version-compat-tests:") -> {
-                    createOrUpdateMediaTestConfigurationGenerationTask(
-                        name,
-                        artifacts,
-                        baseExtension.defaultConfig.minSdk!!,
-                        baseExtension.defaultConfig.testInstrumentationRunner!!,
-                    )
-                }
-                else -> {
                     createTestConfigurationGenerationTask(
-                        name,
-                        artifacts,
+                        variant.name,
+                        variant.artifacts,
+                        // replace minSdk after b/328495232 is fixed
                         baseExtension.defaultConfig.minSdk!!,
-                        baseExtension.defaultConfig.testInstrumentationRunner!!,
-                        baseExtension.defaultConfig.testInstrumentationRunnerArguments
+                        provider { baseExtension.defaultConfig.testInstrumentationRunner!! },
+                        provider { baseExtension.defaultConfig.testInstrumentationRunnerArguments }
                     )
                 }
             }
@@ -450,17 +449,15 @@
     componentsExtension: KotlinMultiplatformAndroidComponentsExtension
 ) {
     componentsExtension.onVariant { variant ->
-            val name = variant.androidTest?.name ?: return@onVariant
-            val artifacts = variant.androidTest?.artifacts ?: return@onVariant
-        kotlinMultiplatformAndroidTarget.compilations.withType(
-            KotlinMultiplatformAndroidTestOnDeviceCompilation::class.java
-        ) {
+        @Suppress("UnstableApiUsage") // usage of HasDeviceTests
+        variant.deviceTests.forEach { deviceTest ->
             createTestConfigurationGenerationTask(
-                name,
-                artifacts,
+                deviceTest.name,
+                deviceTest.artifacts,
+                // replace minSdk after b/328495232 is fixed
                 kotlinMultiplatformAndroidTarget.minSdk!!,
-                it.instrumentationRunner!!,
-                mapOf()
+                deviceTest.instrumentationRunner,
+                deviceTest.instrumentationRunnerArguments,
             )
         }
     }
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/ApkCopyHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/ApkCopyHelper.kt
index 8d29c124..1f3e69a 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/ApkCopyHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/ApkCopyHelper.kt
@@ -21,7 +21,7 @@
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
 import com.android.build.api.variant.BuiltArtifactsLoader
-import com.android.build.api.variant.HasAndroidTest
+import com.android.build.api.variant.HasDeviceTests
 import java.io.File
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
@@ -76,29 +76,27 @@
 fun setupTestApkCopy(project: Project) {
     project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
         onVariants { variant ->
-            var name: String? = null
-            var artifacts: Artifacts? = null
+            fun registerAndAddToBuildOnServer(name: String, artifacts: Artifacts) {
+                val apkCopy =
+                    project.tasks.register("copyTestApk$name", ApkCopyTask::class.java) { task ->
+                        task.apkFolder.set(artifacts.get(SingleArtifact.APK))
+                        task.apkLoader.set(artifacts.getBuiltArtifactsLoader())
+                        val file = "apks/${project.path.substring(1).replace(':', '-')}-$name.apk"
+                        task.outputApk.set(File(project.getDistributionDirectory(), file))
+                    }
+                project.addToBuildOnServer(apkCopy)
+            }
+            @Suppress("UnstableApiUsage") // usage of HasDeviceTests
             when {
-                variant is HasAndroidTest -> {
-                    name = variant.androidTest?.name
-                    artifacts = variant.androidTest?.artifacts
+                variant is HasDeviceTests -> {
+                    variant.deviceTests.forEach { deviceTest ->
+                        registerAndAddToBuildOnServer(deviceTest.name, deviceTest.artifacts)
+                    }
                 }
                 project.plugins.hasPlugin("com.android.test") -> {
-                    name = variant.name
-                    artifacts = variant.artifacts
+                    registerAndAddToBuildOnServer(variant.name, variant.artifacts)
                 }
             }
-            if (name == null || artifacts == null) {
-                return@onVariants
-            }
-            val apkCopy =
-                project.tasks.register("copyTestApk", ApkCopyTask::class.java) { task ->
-                    task.apkFolder.set(artifacts.get(SingleArtifact.APK))
-                    task.apkLoader.set(artifacts.getBuiltArtifactsLoader())
-                    val file = "apks/${project.path.substring(1).replace(':', '-')}-$name.apk"
-                    task.outputApk.set(File(project.getDistributionDirectory(), file))
-                }
-            project.addToBuildOnServer(apkCopy)
         }
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
index 6a7c736..189497c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -36,6 +36,9 @@
 import com.google.common.util.concurrent.ListenableFuture
 import javax.inject.Inject
 import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 
 internal val cameraAdapterIds = atomic(0)
@@ -88,7 +91,9 @@
     }
 
     override fun release(): ListenableFuture<Void> {
-        return threads.scope.launch { useCaseManager.close() }.asListenableFuture()
+        return threads.scope.launch { useCaseManager.close() }.asListenableFuture().apply {
+            addListener({ threads.scope.cancel() }, Dispatchers.Default.asExecutor())
+        }
     }
 
     override fun getCameraInfoInternal(): CameraInfoInternal = cameraInfo
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
index 20b4dfc..04d93f1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
@@ -46,6 +46,9 @@
         if (ControlZoomRatioRangeAssertionErrorQuirk.isEnabled()) {
             quirks.add(ControlZoomRatioRangeAssertionErrorQuirk())
         }
+        if (DisableAbortCapturesOnStopWithSessionProcessorQuirk.isEnabled()) {
+            quirks.add(DisableAbortCapturesOnStopWithSessionProcessorQuirk())
+        }
         if (FlashAvailabilityBufferUnderflowQuirk.isEnabled()) {
             quirks.add(FlashAvailabilityBufferUnderflowQuirk())
         }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DisableAbortCapturesOnStopWithSessionProcessorQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DisableAbortCapturesOnStopWithSessionProcessorQuirk.kt
new file mode 100644
index 0000000..c84cf3b
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DisableAbortCapturesOnStopWithSessionProcessorQuirk.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCaptureSession
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.Quirk
+import androidx.camera.core.impl.SessionProcessor
+
+/**
+ * A quirk to not abort captures on stop during [SessionProcessor] sessions on certain platforms.
+ *
+ * QuirkSummary
+ * - Bug Id: 325088903
+ * - Description: When stopping a capture session, calling [CameraCaptureSession.abortCaptures] may
+ *   cause [SessionProcessor.deInitSession] to hang indefinitely. By default, CameraPipe aborts
+ *   captures on stop to speed up switching between capture sessions. With this quirk, this behavior
+ *   is disabled on devices from selected vendors.
+ * - Device(s): Devices on devices from selected vendors.
+ *
+ * TODO(b/270421716): enable CameraXQuirksClassDetector lint check when kotlin is supported.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class DisableAbortCapturesOnStopWithSessionProcessorQuirk : Quirk {
+    companion object {
+        fun isEnabled(): Boolean {
+            return Build.BRAND.equals("SAMSUNG", ignoreCase = true)
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
index 04cffc6..db8129e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
@@ -23,7 +23,6 @@
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraStream
-import androidx.camera.camera2.pipe.compat.CameraPipeKeys
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
@@ -60,8 +59,42 @@
 ) {
     private val lock = Any()
 
+    enum class State {
+        /**
+         * [CREATED] is the initial state, and indicates that the [SessionProcessorManager] has
+         * been created but not initialized yet.
+         */
+        CREATED,
+
+        /**
+         * [INITIALIZED] indicates that the [SessionProcessor] has been initialized and we've
+         * received the updated session configurations. See also:
+         * [SessionProcessor.deInitSession].
+         */
+        INITIALIZED,
+
+        /**
+         * [STARTED] indicates that we've provided our
+         * [androidx.camera.core.impl.RequestProcessor] implementation to [SessionProcessor].
+         * See also [SessionProcessor.onCaptureSessionStart].
+         */
+        STARTED,
+
+        /**
+         * [CLOSING] indicates that we're ending our capture session, and we'll no longer accept
+         * any further capture requests. See also: [SessionProcessor.onCaptureSessionEnd].
+         */
+        CLOSING,
+
+        /**
+         * [CLOSED] indicates that the underlying capture session has been completely closed
+         * and we've de-initialized the session. See also: [SessionProcessor.deInitSession].
+         */
+        CLOSED,
+    }
+
     @GuardedBy("lock")
-    private var captureSessionStarted = false
+    private var state: State = State.CREATED
 
     @GuardedBy("lock")
     private var sessionOptions = CaptureRequestOptions.Builder().build()
@@ -82,32 +115,39 @@
     internal var sessionConfig: SessionConfig? = null
         set(value) = synchronized(lock) {
             field = checkNotNull(value)
-            if (!captureSessionStarted) return
+            if (state != State.STARTED) return
             checkNotNull(requestProcessor).sessionConfig = value
             sessionOptions =
                 CaptureRequestOptions.Builder.from(value.implementationOptions).build()
             updateOptions()
         }
 
+    internal fun isClosed() = synchronized(lock) {
+        state == State.CLOSED || state == State.CLOSING
+    }
+
     internal fun initialize(
         useCaseManager: UseCaseManager,
         useCases: List<UseCase>,
         timeoutMillis: Long = 5_000L,
+        configure: (UseCaseManager.Companion.UseCaseManagerConfig?) -> Unit,
     ) = scope.launch {
         val sessionConfigAdapter = SessionConfigAdapter(useCases, null)
         val deferrableSurfaces = sessionConfigAdapter.deferrableSurfaces
         val surfaces = getSurfaces(deferrableSurfaces, timeoutMillis)
-        if (!isActive) return@launch
+        if (!isActive) return@launch configure(null)
         if (surfaces.isEmpty()) {
-            Log.error { "Surface list is empty" }
-            return@launch
+            Log.error { "Cannot initialize ${this@SessionProcessorManager}: Surface list is empty" }
+            return@launch configure(null)
         }
         if (surfaces.contains(null)) {
-            Log.error { "Some Surfaces are invalid!" }
+            Log.error {
+                "Cannot initialize ${this@SessionProcessorManager}: Some Surfaces are invalid!"
+            }
             sessionConfigAdapter.reportSurfaceInvalid(
                 deferrableSurfaces[surfaces.indexOf(null)]
             )
-            return@launch
+            return@launch configure(null)
         }
         var previewOutputSurface: OutputSurface? = null
         var captureOutputSurface: OutputSurface? = null
@@ -146,28 +186,40 @@
                 "postviewOutputSurface = $postviewOutputSurface"
         }
 
-        try {
-            DeferrableSurfaces.incrementAll(deferrableSurfaces)
-            postviewDeferrableSurface?.incrementUseCount()
-        } catch (exception: DeferrableSurface.SurfaceClosedException) {
-            sessionConfigAdapter.reportSurfaceInvalid(exception.deferrableSurface)
-            return@launch
-        }
-        val processorSessionConfig = try {
-            sessionProcessor.initSession(
-                cameraInfoInternal,
-                OutputSurfaceConfiguration.create(
-                    previewOutputSurface!!,
-                    captureOutputSurface!!,
-                    analysisOutputSurface,
-                    postviewOutputSurface,
-                ),
-            )
-        } catch (throwable: Throwable) {
-            Log.error(throwable) { "initSession() failed" }
-            DeferrableSurfaces.decrementAll(deferrableSurfaces)
-            postviewDeferrableSurface?.decrementUseCount()
-            throw throwable
+        // IMPORTANT: The critical section (covered by synchronized) is intentionally expanded to
+        // cover the sections where we increment and decrement (on failure) the use count on the
+        // DeferrableSurfaces. This is needed because the SessionProcessorManager could be closed
+        // while we're still initializing, and we need to make sure we either initialize to a point
+        // where all the lifetimes of Surfaces are setup or we don't initialize at all beyond this
+        // point.
+        val processorSessionConfig = synchronized(lock) {
+            if (isClosed()) return@launch configure(null)
+            try {
+                DeferrableSurfaces.incrementAll(deferrableSurfaces)
+                postviewDeferrableSurface?.incrementUseCount()
+            } catch (exception: DeferrableSurface.SurfaceClosedException) {
+                sessionConfigAdapter.reportSurfaceInvalid(exception.deferrableSurface)
+                return@launch configure(null)
+            }
+            try {
+                Log.debug { "Invoking $sessionProcessor SessionProcessor#initSession" }
+                sessionProcessor.initSession(
+                    cameraInfoInternal,
+                    OutputSurfaceConfiguration.create(
+                        previewOutputSurface!!,
+                        captureOutputSurface!!,
+                        analysisOutputSurface,
+                        postviewOutputSurface,
+                    ),
+                ).also {
+                    state = State.INITIALIZED
+                }
+            } catch (throwable: Throwable) {
+                Log.error(throwable) { "initSession() failed" }
+                DeferrableSurfaces.decrementAll(deferrableSurfaces)
+                postviewDeferrableSurface?.decrementUseCount()
+                throw throwable
+            }
         }
 
         // DecrementAll the output surfaces when ProcessorSurface terminates.
@@ -183,7 +235,7 @@
         val cameraGraphConfig = useCaseManager.createCameraGraphConfig(
             processorSessionConfigAdapter,
             streamConfigMap,
-            mapOf(CameraPipeKeys.ignore3ARequiredParameters to true)
+            isExtensions = true,
         )
 
         val useCaseManagerConfig = UseCaseManager.Companion.UseCaseManagerConfig(
@@ -193,25 +245,30 @@
             streamConfigMap,
         )
 
-        useCaseManager.tryResumeUseCaseManager(useCaseManagerConfig)
+        return@launch configure(useCaseManagerConfig)
     }
 
     internal fun onCaptureSessionStart(requestProcessor: RequestProcessorAdapter) {
         var captureConfigsToIssue: List<CaptureConfig>?
         var captureCallbacksToIssue: List<CaptureCallback>?
         synchronized(lock) {
-            check(!captureSessionStarted)
+            if (state != State.INITIALIZED) {
+                Log.warn { "onCaptureSessionStart called on an uninitialized extensions session" }
+                return
+            }
             requestProcessor.sessionConfig = sessionConfig
             this.requestProcessor = requestProcessor
-            captureSessionStarted = true
 
             captureConfigsToIssue = pendingCaptureConfigs
             captureCallbacksToIssue = pendingCaptureCallbacks
             pendingCaptureConfigs = null
             pendingCaptureCallbacks = null
+
+            Log.debug { "Invoking SessionProcessor#onCaptureSessionStart" }
+            sessionProcessor.onCaptureSessionStart(requestProcessor)
+
+            state = State.STARTED
         }
-        Log.debug { "Invoking SessionProcessor#onCaptureSessionStart" }
-        sessionProcessor.onCaptureSessionStart(requestProcessor)
         startRepeating(object : CaptureCallback {})
         captureConfigsToIssue?.let { captureConfigs ->
             submitCaptureConfigs(captureConfigs, checkNotNull(captureCallbacksToIssue))
@@ -220,31 +277,38 @@
 
     internal fun startRepeating(captureCallback: CaptureCallback) {
         synchronized(lock) {
-            if (!captureSessionStarted) return
+            if (state != State.STARTED) return
+            Log.debug { "Invoking SessionProcessor#startRepeating" }
+            sessionProcessor.startRepeating(captureCallback)
         }
-        Log.debug { "Invoking SessionProcessor#startRepeating" }
-        sessionProcessor.startRepeating(captureCallback)
     }
 
     internal fun stopRepeating() {
         synchronized(lock) {
-            if (!captureSessionStarted) return
+            if (state != State.STARTED) return
+            Log.debug { "Invoking SessionProcessor#stopRepeating" }
+            sessionProcessor.stopRepeating()
         }
-        Log.debug { "Invoking SessionProcessor#stopRepeating" }
-        sessionProcessor.stopRepeating()
     }
 
     internal fun submitCaptureConfigs(
         captureConfigs: List<CaptureConfig>,
         captureCallbacks: List<CaptureCallback>,
-    ) {
+    ) = synchronized(lock) {
         check(captureConfigs.size == captureCallbacks.size)
-        synchronized(lock) {
-            if (!captureSessionStarted) {
-                pendingCaptureConfigs = captureConfigs
-                pendingCaptureCallbacks = captureCallbacks
-                return
+        if (state != State.STARTED) {
+            // The lifetime of image capture requests is separate from the extensions lifetime.
+            // It is therefore possible for capture requests to be issued when the capture session
+            // hasn't yet started (before invoking SessionProcessor.onCaptureSessionStart). This is
+            // a copy of camera-camera2's behavior where it stores the last capture configs that
+            // weren't submitted.
+            Log.info {
+                "SessionProcessor#submitCaptureConfigs: Session not yet started. " +
+                    "The capture requests will be submitted later"
             }
+            pendingCaptureConfigs = captureConfigs
+            pendingCaptureCallbacks = captureCallbacks
+            return
         }
         for ((config, callback) in captureConfigs.zip(captureCallbacks)) {
             if (config.templateType == CameraDevice.TEMPLATE_STILL_CAPTURE) {
@@ -281,10 +345,30 @@
         }
     }
 
-    internal fun onCaptureSessionEnd() {
-        sessionProcessor.onCaptureSessionEnd()
+    internal fun prepareClose() = synchronized(lock) {
+        if (state == State.STARTED) {
+            sessionProcessor.onCaptureSessionEnd()
+        }
+        // If we have an initialized SessionProcessor session (i.e., initSession was called), we
+        // need to make sure close() invokes deInitSession and only does it when necessary.
+        if (state == State.INITIALIZED || state == State.STARTED) {
+            state = State.CLOSING
+        } else {
+            state = State.CLOSED
+        }
     }
 
+    internal fun close() = synchronized(lock) {
+        // These states indicate that we had previously initialized a session (but not yet
+        // de-initialized), and thus we need to de-initialize the session here.
+        if (state == State.INITIALIZED || state == State.STARTED || state == State.CLOSING) {
+            Log.debug { "Invoking $sessionProcessor SessionProcessor#deInitSession" }
+            sessionProcessor.deInitSession()
+        }
+        state = State.CLOSED
+    }
+
+    @GuardedBy("lock")
     private fun updateOptions() {
         val builder = Camera2ImplConfig.Builder().apply {
             insertAllOptions(sessionOptions)
@@ -293,10 +377,6 @@
         sessionProcessor.setParameters(builder.build())
     }
 
-    internal fun close() {
-        sessionProcessor.deInitSession()
-    }
-
     companion object {
         private suspend fun getSurfaces(
             deferrableSurfaces: List<DeferrableSurface>,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index 5710e9c..f802d66 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -22,6 +22,7 @@
 import android.hardware.camera2.CaptureRequest
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.GraphState.GraphStateError
 import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
 import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
 import androidx.camera.camera2.pipe.RequestTemplate
@@ -42,7 +43,7 @@
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 internal val useCaseCameraIds = atomic(0)
@@ -99,6 +100,7 @@
 ) : UseCaseCamera {
     private val debugId = useCaseCameraIds.incrementAndGet()
     private val closed = atomic(false)
+    private lateinit var stateCollectJob: Job
 
     override var runningUseCases = setOf<UseCase>()
         set(value) {
@@ -129,12 +131,20 @@
         useCaseGraphConfig.apply {
             cameraStateAdapter.onGraphUpdated(graph)
         }
-        threads.scope.launch {
+        stateCollectJob = threads.scope.launch {
             useCaseGraphConfig.apply {
                 graph.graphState.collect {
                     cameraStateAdapter.onGraphStateUpdated(graph, it)
-                    if (closed.value && it is GraphStateStopped) {
-                        cancel()
+
+                    // Even if the UseCaseCamera is closed, we should still update the GraphState
+                    // before cancelling the job, because it could be the last UseCaseCamera created
+                    // (i.e., no new UseCaseCamera to update CameraStateAdapter that this one as
+                    // stopped/closed).
+                    if (closed.value &&
+                        it is GraphStateStopped ||
+                        it is GraphStateError
+                    ) {
+                        stateCollectJob.cancel()
                     }
 
                     // TODO: b/323614735: Technically our RequestProcessor implementation could be
@@ -168,9 +178,14 @@
             threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
                 debug { "Closing $this" }
                 requestControl.close()
-                sessionProcessorManager?.onCaptureSessionEnd()
+                sessionProcessorManager?.prepareClose()
                 useCaseGraphConfig.graph.close()
-                sessionProcessorManager?.close()
+                if (sessionProcessorManager != null) {
+                    useCaseGraphConfig.graph.graphState.first {
+                        it is GraphStateStopped || it is GraphStateError
+                    }
+                    sessionProcessorManager.close()
+                }
                 useCaseSurfaceManager.stopAsync().await()
             }
         } else {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index a2447e6..8aff64a8 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -26,6 +26,7 @@
 import android.os.Build
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.OperatingMode
 import androidx.camera.camera2.pipe.CameraId
@@ -33,6 +34,7 @@
 import androidx.camera.camera2.pipe.CameraStream
 import androidx.camera.camera2.pipe.OutputStream
 import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.compat.CameraPipeKeys
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.EncoderProfilesProviderAdapter
@@ -43,6 +45,7 @@
 import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnDisconnectQuirk
 import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnVideoQuirk
 import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.DisableAbortCapturesOnStopWithSessionProcessorQuirk
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
@@ -64,8 +67,10 @@
 import androidx.camera.core.impl.stabilization.StabilizationMode
 import javax.inject.Inject
 import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.joinAll
+import kotlinx.coroutines.runBlocking
 
 /**
  * This class keeps track of the currently attached and active [UseCase]'s for a specific camera.
@@ -143,6 +148,12 @@
     @GuardedBy("lock")
     private var deferredUseCaseManagerConfig: UseCaseManagerConfig? = null
 
+    @GuardedBy("lock")
+    private var pendingSessionProcessorInitialization = false
+
+    @GuardedBy("lock")
+    private val pendingUseCasesToNotifyCameraControlReady = mutableSetOf<UseCase>()
+
     private val meteringRepeating by lazy {
         MeteringRepeating.Builder(
             cameraProperties,
@@ -187,7 +198,7 @@
      * changes are identified (i.e., a new use case is added), the subsequent actions would trigger
      * a recreation of the current CameraGraph if there is one.
      */
-    fun attach(useCases: List<UseCase>) = synchronized(lock) {
+    fun attach(useCases: List<UseCase>): Unit = synchronized(lock) {
         if (useCases.isEmpty()) {
             Log.warn { "Attach [] from $this (Ignored)" }
             return
@@ -209,9 +220,13 @@
             }
         }
 
-        unattachedUseCases.forEach { useCase ->
-            // Notify CameraControl is ready after the UseCaseCamera is created
-            useCase.onCameraControlReady()
+        if (sessionProcessor != null || !shouldCreateCameraGraphImmediately) {
+            pendingUseCasesToNotifyCameraControlReady.addAll(unattachedUseCases)
+        } else {
+            unattachedUseCases.forEach { useCase ->
+                // Notify CameraControl is ready after the UseCaseCamera is created
+                useCase.onCameraControlReady()
+            }
         }
     }
 
@@ -220,7 +235,7 @@
      * changes are identified (i.e., an existing use case is removed), the subsequent actions would
      * trigger a recreation of the current CameraGraph.
      */
-    fun detach(useCases: List<UseCase>) = synchronized(lock) {
+    fun detach(useCases: List<UseCase>): Unit = synchronized(lock) {
         if (useCases.isEmpty()) {
             Log.warn { "Detaching [] from $this (Ignored)" }
             return
@@ -247,6 +262,7 @@
             }
             refreshAttachedUseCases(attachedUseCases)
         }
+        pendingUseCasesToNotifyCameraControlReady.removeAll(useCases)
     }
 
     /**
@@ -305,6 +321,13 @@
 
     @GuardedBy("lock")
     private fun refreshRunningUseCases() {
+        // refreshRunningUseCases() is called after we activate, deactivate, update or have finished
+        // attaching use cases. If the SessionProcessor is still being initialized, we cannot
+        // refresh the current set of running use cases (we don't have a UseCaseCamera), but we
+        // can safely abort here, because once the SessionProcessor is initialized, we'll resume
+        // the process of creating UseCaseCamera components, finish attaching use cases and finally
+        // invoke refreshingRunningUseCases().
+        if (pendingSessionProcessorInitialization) return
         val runningUseCases = getRunningUseCases()
         when {
             shouldAddRepeatingUseCase(runningUseCases) -> addRepeatingUseCase()
@@ -313,23 +336,36 @@
         }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @GuardedBy("lock")
     private fun refreshAttachedUseCases(newUseCases: Set<UseCase>) {
         val useCases = newUseCases.toList()
 
         // Close prior camera graph
-        camera.let {
+        camera.let { useCaseCamera ->
             _activeComponent = null
-            it?.close()?.let { closingJob ->
-                closingCameraJobs.add(closingJob)
-                closingJob.invokeOnCompletion {
-                    synchronized(lock) {
-                        closingCameraJobs.remove(closingJob)
+            useCaseCamera?.close()?.let { closingJob ->
+                if (sessionProcessorManager != null) {
+                    // If the current session was created for extensions. We need to make sure
+                    // the closing procedures are done. This is needed because the same
+                    // SessionProcessor instance may be reused in the next extensions session, and
+                    // we need to make sure we de-initialize the current SessionProcessor session.
+                    runBlocking { closingJob.join() }
+                } else {
+                    closingCameraJobs.add(closingJob)
+                    closingJob.invokeOnCompletion {
+                        synchronized(lock) {
+                            closingCameraJobs.remove(closingJob)
+                        }
                     }
                 }
             }
         }
-        sessionProcessorManager = null
+        sessionProcessorManager?.let {
+            it.close()
+            sessionProcessorManager = null
+            pendingSessionProcessorInitialization = false
+        }
 
         // Update list of active useCases
         if (useCases.isEmpty()) {
@@ -340,14 +376,45 @@
             return
         }
 
+        if (sessionProcessor != null || !shouldCreateCameraGraphImmediately) {
+            // We will need to set the UseCaseCamera to null since the new UseCaseCamera along with
+            // its respective CameraGraph configurations won't be ready until:
+            //
+            // - SessionProcessorManager finishes the initialization, _acquires the lock_, and
+            //    resume UseCaseManager successfully
+            // - And/or, the UseCaseManager is ready to be resumed under concurrent camera settings.
+            for (control in allControls) {
+                control.useCaseCamera = null
+            }
+        }
+
         if (sessionProcessor != null) {
             Log.debug { "Setting up UseCaseManager with SessionProcessorManager" }
             sessionProcessorManager = SessionProcessorManager(
                 sessionProcessor!!,
                 cameraInfoInternal.get(),
                 useCaseThreads.get().scope,
-            ).also {
-                it.initialize(this, useCases)
+            ).also { manager ->
+                pendingSessionProcessorInitialization = true
+                manager.initialize(this, useCases) { config ->
+                    synchronized(lock) {
+                        if (manager.isClosed()) {
+                            // We've been cancelled by other use case transactions. This means the
+                            // attached set of use cases have been updated in the meantime, and the
+                            // UseCaseManagerConfig we have here is obsolete, so we can simply abort
+                            // here.
+                            return@initialize
+                        }
+                        if (config == null) {
+                            Log.error { "Failed to initialize SessionProcessor" }
+                            manager.close()
+                            sessionProcessorManager = null
+                            return@initialize
+                        }
+                        pendingSessionProcessorInitialization = false
+                        this@UseCaseManager.tryResumeUseCaseManager(config)
+                    }
+                }
             }
             return
         } else {
@@ -361,10 +428,12 @@
                 graphConfig,
                 streamConfigMap
             )
-            tryResumeUseCaseManager(useCaseManagerConfig)
+            this.tryResumeUseCaseManager(useCaseManagerConfig)
         }
     }
 
+    @VisibleForTesting
+    @GuardedBy("lock")
     internal fun tryResumeUseCaseManager(useCaseManagerConfig: UseCaseManagerConfig) {
         if (!shouldCreateCameraGraphImmediately) {
             deferredUseCaseManagerConfig = useCaseManagerConfig
@@ -374,12 +443,11 @@
         beginComponentCreation(useCaseManagerConfig, cameraGraph)
     }
 
-    internal fun resumeDeferredComponentCreation(cameraGraph: CameraGraph) {
-        val config = synchronized(lock) { deferredUseCaseManagerConfig }
-        checkNotNull(config)
-        beginComponentCreation(config, cameraGraph)
+    internal fun resumeDeferredComponentCreation(cameraGraph: CameraGraph) = synchronized(lock) {
+        beginComponentCreation(checkNotNull(deferredUseCaseManagerConfig), cameraGraph)
     }
 
+    @GuardedBy("lock")
     private fun beginComponentCreation(
         useCaseManagerConfig: UseCaseManagerConfig,
         cameraGraph: CameraGraph
@@ -416,6 +484,12 @@
 
             refreshRunningUseCases()
         }
+
+        Log.debug { "Notifying $pendingUseCasesToNotifyCameraControlReady camera control ready" }
+        for (useCase in pendingUseCasesToNotifyCameraControlReady) {
+            useCase.onCameraControlReady()
+        }
+        pendingUseCasesToNotifyCameraControlReady.clear()
     }
 
     @GuardedBy("lock")
@@ -484,7 +558,7 @@
     internal fun createCameraGraphConfig(
         sessionConfigAdapter: SessionConfigAdapter,
         streamConfigMap: MutableMap<CameraStream.Config, DeferrableSurface>,
-        defaultParameters: Map<*, Any?> = emptyMap<Any, Any?>(),
+        isExtensions: Boolean = false,
     ): CameraGraph.Config {
         return Companion.createCameraGraphConfig(
             sessionConfigAdapter,
@@ -494,7 +568,7 @@
             cameraConfig,
             cameraQuirks,
             cameraGraphFlags,
-            defaultParameters,
+            isExtensions,
         )
     }
 
@@ -593,7 +667,7 @@
             cameraConfig: CameraConfig,
             cameraQuirks: CameraQuirks,
             cameraGraphFlags: CameraGraph.Flags?,
-            defaultParameters: Map<*, Any?> = emptyMap<Any, Any?>(),
+            isExtensions: Boolean = false,
         ): CameraGraph.Config {
             var containsVideo = false
             var operatingMode = OperatingMode.NORMAL
@@ -648,7 +722,9 @@
                 }
             }
             val shouldCloseCaptureSessionOnDisconnect =
-                if (CameraQuirks.isImmediateSurfaceReleaseAllowed()) {
+                if (isExtensions) {
+                    true
+                } else if (CameraQuirks.isImmediateSurfaceReleaseAllowed()) {
                     // If we can release Surfaces immediately, we'll finalize the session when the
                     // camera graph is closed (through FinalizeSessionOnCloseQuirk), and thus we won't
                     // need to explicitly close the capture session.
@@ -664,12 +740,26 @@
                 }
             val shouldCloseCameraDeviceOnClose =
                 DeviceQuirks[CloseCameraDeviceOnCameraGraphCloseQuirk::class.java] != null
+            val shouldAbortCapturesOnStop =
+                if (isExtensions &&
+                    DeviceQuirks[DisableAbortCapturesOnStopWithSessionProcessorQuirk::class.java] !=
+                    null
+                ) {
+                    false
+                } else {
+                    /**
+                     * @see [CameraGraph.Flags.abortCapturesOnStop]
+                     */
+                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+                }
 
             val combinedFlags = cameraGraphFlags?.copy(
+                abortCapturesOnStop = shouldAbortCapturesOnStop,
                 quirkCloseCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
                 quirkCloseCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
             )
                 ?: CameraGraph.Flags(
+                    abortCapturesOnStop = shouldAbortCapturesOnStop,
                     quirkCloseCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
                     quirkCloseCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
                 )
@@ -694,6 +784,14 @@
                     videoStabilizationMode = CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON
                 }
             }
+            val defaultParameters: Map<*, Any?> = if (isExtensions) {
+                mapOf(CameraPipeKeys.ignore3ARequiredParameters to true)
+            } else {
+                emptyMap<Any, Any?>()
+            } + mapOf(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE to videoStabilizationMode)
+
+            // TODO: b/327517884 - Add a quirk to not abort captures on stop for certain OEMs during
+            //   extension sessions.
 
             // Build up a config (using TEMPLATE_PREVIEW by default)
             return CameraGraph.Config(
@@ -702,9 +800,7 @@
                 exclusiveStreamGroups = streamGroupMap.values.toList(),
                 sessionMode = operatingMode,
                 defaultListeners = listOf(callbackMap, requestListener),
-                defaultParameters = defaultParameters + mapOf(
-                    CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE to videoStabilizationMode
-                ),
+                defaultParameters = defaultParameters,
                 flags = combinedFlags,
             )
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index 7e3c3a6..23f6c65 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -33,6 +33,7 @@
 import com.google.common.util.concurrent.ListenableFuture
 import com.google.common.util.concurrent.MoreExecutors
 import junit.framework.TestCase
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.asCoroutineDispatcher
 import org.junit.Rule
 import org.junit.Test
@@ -150,14 +151,19 @@
 class FakeTestUseCase(
     config: FakeUseCaseConfig,
 ) : FakeUseCase(config) {
+    var cameraControlReady = false
 
     fun setupSessionConfig(sessionConfigBuilder: SessionConfig.Builder) {
         updateSessionConfig(sessionConfigBuilder.build())
         notifyActive()
     }
+
+    override fun onCameraControlReady() {
+        cameraControlReady = true
+    }
 }
 
-class TestDeferrableSurface : DeferrableSurface() {
+open class TestDeferrableSurface : DeferrableSurface() {
     private val surfaceTexture = SurfaceTexture(0).also {
         it.setDefaultBufferSize(0, 0)
     }
@@ -172,3 +178,13 @@
         surfaceTexture.release()
     }
 }
+
+class BlockingTestDeferrableSurface : TestDeferrableSurface() {
+    private val deferred = CompletableDeferred<Surface>()
+
+    override fun provideSurface(): ListenableFuture<Surface> {
+        return deferred.asListenableFuture()
+    }
+
+    fun resume() = deferred.complete(testSurface)
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index 2b3445c..d2216fb 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -176,12 +176,22 @@
             return CompletableDeferred(Result3A(Result3A.Status.OK))
         }
 
+        override suspend fun lock3AForCapture(
+            triggerAf: Boolean,
+            waitForAwb: Boolean,
+            frameLimit: Int,
+            timeLimitNs: Long
+        ): Deferred<Result3A> {
+            lock3AForCaptureSemaphore.release()
+            return CompletableDeferred(Result3A(Result3A.Status.OK))
+        }
+
         override fun submit(requests: List<Request>) {
             requestHandler(requests)
             submitSemaphore.release()
         }
 
-        override suspend fun unlock3APostCapture(): Deferred<Result3A> {
+        override suspend fun unlock3APostCapture(cancelAf: Boolean): Deferred<Result3A> {
             unlock3APostCaptureSemaphore.release()
             return CompletableDeferred(Result3A(Result3A.Status.OK))
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
index db12111..f65dfdc 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
@@ -21,7 +21,6 @@
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.FakeTestUseCase
 import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -29,35 +28,36 @@
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraInfoAdapterCreator
-import androidx.camera.core.CameraInfo
-import androidx.camera.core.ImageAnalysis
+import androidx.camera.camera2.pipe.integration.testing.FakeSessionProcessor
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
 import androidx.camera.core.impl.CaptureConfig
-import androidx.camera.core.impl.OutputSurfaceConfiguration
-import androidx.camera.core.impl.RequestProcessor
 import androidx.camera.core.impl.SessionConfig
-import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
-import androidx.camera.core.impl.SessionProcessorSurface
 import androidx.camera.core.streamsharing.StreamSharing
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
 import androidx.testutils.MainDispatcherRule
 import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertNull
 import kotlin.test.Test
+import kotlin.test.assertNotNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 
+@ExperimentalCoroutinesApi
 @OptIn(ExperimentalCamera2Interop::class)
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.M)
@@ -69,87 +69,7 @@
     @get:Rule
     val mainDispatcherRule = MainDispatcherRule(testDispatcher)
 
-    private val fakeSessionProcessor = object : SessionProcessor {
-        val previewOutputConfigId = 0
-        val imageCaptureOutputConfigId = 1
-        val imageAnalysisOutputConfigId = 2
-
-        var lastParameters: androidx.camera.core.impl.Config? = null
-        var startCapturesCount = 0
-
-        override fun initSession(
-            cameraInfo: CameraInfo,
-            outputSurfaceConfiguration: OutputSurfaceConfiguration,
-        ): SessionConfig {
-            Log.debug { "$this#initSession" }
-            val previewSurface = SessionProcessorSurface(
-                outputSurfaceConfiguration.previewOutputSurface.surface,
-                previewOutputConfigId
-            ).also {
-                it.setContainerClass(Preview::class.java)
-            }
-            val imageCaptureSurface = SessionProcessorSurface(
-                outputSurfaceConfiguration.imageCaptureOutputSurface.surface,
-                imageCaptureOutputConfigId
-            ).also {
-                it.setContainerClass(ImageCapture::class.java)
-            }
-            val imageAnalysisSurface =
-                outputSurfaceConfiguration.imageAnalysisOutputSurface?.surface?.let { surface ->
-                    SessionProcessorSurface(
-                        surface,
-                        imageAnalysisOutputConfigId
-                    ).also {
-                        it.setContainerClass(ImageAnalysis::class.java)
-                    }
-                }
-            return SessionConfig.Builder().apply {
-                setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
-                addSurface(previewSurface)
-                addSurface(imageCaptureSurface)
-                imageAnalysisSurface?.let { addSurface(it) }
-            }.build()
-        }
-
-        override fun deInitSession() {
-            Log.debug { "$this#deInitSession" }
-        }
-
-        override fun setParameters(config: androidx.camera.core.impl.Config) {
-            Log.debug { "$this#setParameters" }
-            lastParameters = config
-        }
-
-        override fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
-            Log.debug { "$this#onCaptureSessionStart" }
-        }
-
-        override fun onCaptureSessionEnd() {
-            TODO("Not yet implemented")
-        }
-
-        override fun startRepeating(callback: CaptureCallback): Int {
-            Log.debug { "$this#startRepeating" }
-            return 0
-        }
-
-        override fun stopRepeating() {
-            Log.debug { "$this#stopRepeating" }
-        }
-
-        override fun startCapture(
-            postviewEnabled: Boolean,
-            callback: CaptureCallback
-        ): Int {
-            Log.debug { "$this#startCapture" }
-            startCapturesCount++
-            return 0
-        }
-
-        override fun abortCapture(captureSequenceId: Int) {
-            TODO("Not yet implemented")
-        }
-    }
+    private val fakeSessionProcessor = FakeSessionProcessor()
     private val fakeCameraId = CameraId.fromCamera2Id("0")
     private val fakeCameraInfoAdapter = FakeCameraInfoAdapterCreator.createCameraInfoAdapter(
         fakeCameraId
@@ -178,9 +98,12 @@
         sessionProcessorManager.initialize(
             useCaseManager,
             listOf(fakePreviewUseCase, fakeImageCaptureUseCase)
-        ).join()
-        verify(useCaseManager).createCameraGraphConfig(any(), any(), any())
-        verify(useCaseManager).tryResumeUseCaseManager(any())
+        ) { useCaseManagerConfig ->
+            assertNotNull(useCaseManagerConfig)
+        }
+
+        advanceUntilIdle()
+        verify(useCaseManager).createCameraGraphConfig(any(), any(), eq(true))
     }
 
     @Test
@@ -203,9 +126,12 @@
         sessionProcessorManager.initialize(
             useCaseManager,
             listOf(fakeStreamSharingUseCase, fakeImageCaptureUseCase)
-        ).join()
-        verify(useCaseManager).createCameraGraphConfig(any(), any(), any())
-        verify(useCaseManager).tryResumeUseCaseManager(any())
+        ) { useCaseManagerConfig ->
+            assertNotNull(useCaseManagerConfig)
+        }
+
+        advanceUntilIdle()
+        verify(useCaseManager).createCameraGraphConfig(any(), any(), eq(true))
     }
 
     @Test
@@ -228,8 +154,11 @@
         sessionProcessorManager.initialize(
             useCaseManager,
             listOf(fakePreviewUseCase, fakeImageCaptureUseCase)
-        ).join()
+        ) { useCaseManagerConfig ->
+            assertNotNull(useCaseManagerConfig)
+        }
         sessionProcessorManager.sessionConfig = SessionConfig.Builder().build()
+        advanceUntilIdle()
 
         val mockRequestProcessorAdapter: RequestProcessorAdapter = mock()
         sessionProcessorManager.onCaptureSessionStart(mockRequestProcessorAdapter)
@@ -256,6 +185,33 @@
         assertThat(fakeSessionProcessor.startCapturesCount).isEqualTo(1)
     }
 
+    @Test
+    fun testSessionProcessorManagerConfiguresNullWhenClosed() = runTest {
+        val useCaseManager: UseCaseManager = mock()
+        whenever(useCaseManager.createCameraGraphConfig(any(), any(), any())).thenReturn(
+            CameraGraph.Config(fakeCameraId, emptyList())
+        )
+        val fakePreviewUseCase = createFakeTestUseCase(
+            "Preview",
+            CameraDevice.TEMPLATE_PREVIEW,
+            Preview::class.java
+        )
+        val fakeImageCaptureUseCase = createFakeTestUseCase(
+            "ImageCapture",
+            CameraDevice.TEMPLATE_STILL_CAPTURE,
+            ImageCapture::class.java
+        )
+
+        sessionProcessorManager.prepareClose()
+        sessionProcessorManager.close()
+        sessionProcessorManager.initialize(
+            useCaseManager,
+            listOf(fakePreviewUseCase, fakeImageCaptureUseCase)
+        ) { useCaseManagerConfig ->
+            assertNull(useCaseManagerConfig)
+        }
+    }
+
     private fun <T> createFakeTestUseCase(
         name: String,
         template: Int,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index 4685e28..127b304 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -18,14 +18,18 @@
 
 import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
 import android.os.Build
 import android.util.Size
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.integration.adapter.BlockingTestDeferrableSurface
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
+import androidx.camera.camera2.pipe.integration.adapter.FakeTestUseCase
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.adapter.TestDeferrableSurface
 import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
@@ -35,25 +39,35 @@
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.integration.testing.FakeCamera2CameraControlCompat
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
+import androidx.camera.camera2.pipe.integration.testing.FakeSessionProcessor
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraComponentBuilder
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCase
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.fakes.FakeUseCase
+import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.asCoroutineDispatcher
+import junit.framework.TestCase.assertNotNull
+import junit.framework.TestCase.assertNull
+import junit.framework.TestCase.assertTrue
+import kotlin.test.assertFalse
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,6 +77,7 @@
 import org.robolectric.shadows.ShadowCameraManager
 import org.robolectric.shadows.StreamConfigurationMapBuilder
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class UseCaseManagerTest {
@@ -72,19 +87,7 @@
     }.build()
     private val useCaseManagerList = mutableListOf<UseCaseManager>()
     private val useCaseList = mutableListOf<UseCase>()
-    private val useCaseThreads by lazy {
-        val dispatcher = Dispatchers.Default
-        val cameraScope = CoroutineScope(
-            Job() +
-                dispatcher
-        )
-
-        UseCaseThreads(
-            cameraScope,
-            dispatcher.asExecutor(),
-            dispatcher
-        )
-    }
+    private lateinit var useCaseThreads: UseCaseThreads
 
     @After
     fun tearDown() = runBlocking {
@@ -93,8 +96,9 @@
     }
 
     @Test
-    fun enabledUseCasesEmpty_whenUseCaseAttachedOnly() {
+    fun enabledUseCasesEmpty_whenUseCaseAttachedOnly() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val useCase = createPreview()
 
@@ -107,8 +111,9 @@
     }
 
     @Test
-    fun enabledUseCasesNotEmpty_whenUseCaseEnabled() {
+    fun enabledUseCasesNotEmpty_whenUseCaseEnabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val useCase = createPreview()
         useCaseManager.attach(listOf(useCase))
@@ -122,8 +127,80 @@
     }
 
     @Test
-    fun meteringRepeatingNotEnabled_whenPreviewEnabled() {
+    fun attachingUseCasesWithSessionProcessor_ShouldSucceed() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val previewUseCase = createFakePreview()
+        val imageCaptureUseCase = createFakeImageCapture()
+
+        val fakeSessionProcessor: SessionProcessor = FakeSessionProcessor()
+
+        // Act
+        useCaseManager.sessionProcessor = fakeSessionProcessor
+        useCaseManager.activate(previewUseCase)
+        useCaseManager.activate(imageCaptureUseCase)
+        useCaseManager.attach(listOf(previewUseCase, imageCaptureUseCase))
+        advanceUntilIdle()
+
+        // Assert
+        assertNotNull(useCaseManager.camera)
+        assertThat(useCaseManager.camera!!.runningUseCases).containsExactly(
+            previewUseCase,
+            imageCaptureUseCase
+        )
+    }
+
+    @Test
+    fun attachingUseCases_ShouldSupersedeUseCasesPendingInitialization() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val previewDeferrableSurface = createBlockingTestDeferrableSurface(Preview::class.java)
+        val imageCaptureDeferrableSurface =
+            createBlockingTestDeferrableSurface(ImageCapture::class.java)
+        val imageAnalysisDeferrableSurface =
+            createBlockingTestDeferrableSurface(ImageAnalysis::class.java)
+        val previewUseCase = createFakePreview(previewDeferrableSurface)
+        val imageCaptureUseCase = createFakeImageCapture(imageCaptureDeferrableSurface)
+        val imageAnalysisUseCase = createFakeImageAnalysis(imageAnalysisDeferrableSurface)
+        val fakeSessionProcessor = FakeSessionProcessor()
+
+        // Act
+        useCaseManager.sessionProcessor = fakeSessionProcessor
+        useCaseManager.activate(previewUseCase)
+        useCaseManager.activate(imageCaptureUseCase)
+        useCaseManager.attach(listOf(previewUseCase, imageCaptureUseCase))
+        advanceUntilIdle()
+        // Here SessionProcessorProcessor.initialize would stall due to not getting its Surfaces.
+        // When initialization is still pending, the current UseCaseCamera should be null (i.e.,
+        // no attached or running use cases).
+        assertNull(useCaseManager.camera)
+
+        // Attaching an ImageAnalysis use case, which should refresh the attached use cases, and
+        // supersede the current set of use cases.
+        useCaseManager.activate(imageAnalysisUseCase)
+        useCaseManager.attach(listOf(imageAnalysisUseCase))
+        // Resume the DeferrableSurfaces to allow them to be retrieved.
+        previewDeferrableSurface.resume()
+        imageCaptureDeferrableSurface.resume()
+        imageAnalysisDeferrableSurface.resume()
+        advanceUntilIdle()
+
+        // Assert
+        assertNotNull(useCaseManager.camera)
+        // Check that the new set of running use cases is Preview, ImageCapture and ImageAnalysis.
+        assertThat(useCaseManager.camera!!.runningUseCases).containsExactly(
+            previewUseCase,
+            imageCaptureUseCase,
+            imageAnalysisUseCase
+        )
+    }
+
+    @Test
+    fun meteringRepeatingNotEnabled_whenPreviewEnabled() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val preview = createPreview()
         val imageCapture = createImageCapture()
@@ -139,8 +216,9 @@
     }
 
     @Test
-    fun meteringRepeatingEnabled_whenOnlyImageCaptureEnabled() {
+    fun meteringRepeatingEnabled_whenOnlyImageCaptureEnabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
@@ -159,8 +237,9 @@
     }
 
     @Test
-    fun meteringRepeatingDisabled_whenPreviewBecomesEnabled() {
+    fun meteringRepeatingDisabled_whenPreviewBecomesEnabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
@@ -177,8 +256,9 @@
     }
 
     @Test
-    fun meteringRepeatingEnabled_afterAllUseCasesButImageCaptureDisabled() {
+    fun meteringRepeatingEnabled_afterAllUseCasesButImageCaptureDisabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val preview = createPreview()
         val imageCapture = createImageCapture()
@@ -200,8 +280,9 @@
     }
 
     @Test
-    fun onlyOneUseCaseCameraBuilt_whenAllUseCasesButImageCaptureDisabled() {
+    fun onlyOneUseCaseCameraBuilt_whenAllUseCasesButImageCaptureDisabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseCameraBuilder = FakeUseCaseCameraComponentBuilder()
         val useCaseManager = createUseCaseManager(
             useCaseCameraComponentBuilder = useCaseCameraBuilder
@@ -222,8 +303,9 @@
     }
 
     @Test
-    fun meteringRepeatingDisabled_whenAllUseCasesDisabled() {
+    fun meteringRepeatingDisabled_whenAllUseCasesDisabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
@@ -238,8 +320,9 @@
     }
 
     @Test
-    fun onlyOneUseCaseCameraBuilt_whenAllUseCasesDisabled() {
+    fun onlyOneUseCaseCameraBuilt_whenAllUseCasesDisabled() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseCameraBuilder = FakeUseCaseCameraComponentBuilder()
         val useCaseManager = createUseCaseManager(
             useCaseCameraComponentBuilder = useCaseCameraBuilder
@@ -258,8 +341,9 @@
     }
 
     @Test
-    fun onStateAttachedInvokedExactlyOnce_whenUseCaseAttachedAndMeteringRepeatingAdded() {
+    fun onStateAttachedInvokedExactlyOnce_whenUseCaseAttachedAndMeteringRepeatingAdded() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val useCaseManager = createUseCaseManager()
         val imageCapture = createImageCapture()
         val useCase = FakeUseCase().also {
@@ -276,24 +360,27 @@
     }
 
     @Test
-    fun onStateAttachedInvokedExactlyOnce_whenUseCaseAttachedAndMeteringRepeatingNotAdded() {
-        // Arrange
-        val useCaseManager = createUseCaseManager()
-        val preview = createPreview()
-        val useCase = FakeUseCase()
+    fun onStateAttachedInvokedExactlyOnce_whenUseCaseAttachedAndMeteringRepeatingNotAdded() =
+        runTest {
+            // Arrange
+            initializeUseCaseThreads(this)
+            val useCaseManager = createUseCaseManager()
+            val preview = createPreview()
+            val useCase = FakeUseCase()
 
-        // Act
-        useCaseManager.activate(preview)
-        useCaseManager.activate(useCase)
-        useCaseManager.attach(listOf(preview, useCase))
+            // Act
+            useCaseManager.activate(preview)
+            useCaseManager.activate(useCase)
+            useCaseManager.attach(listOf(preview, useCase))
 
-        // Assert
-        assertThat(useCase.stateAttachedCount).isEqualTo(1)
-    }
+            // Assert
+            assertThat(useCase.stateAttachedCount).isEqualTo(1)
+        }
 
     @Test
-    fun controlsNotified_whenRunningUseCasesChanged() {
+    fun controlsNotified_whenRunningUseCasesChanged() = runTest {
         // Arrange
+        initializeUseCaseThreads(this)
         val fakeControl = object : UseCaseCameraControl, RunningUseCasesChangeListener {
             var runningUseCases: Set<UseCase> = emptySet()
 
@@ -309,7 +396,9 @@
             override fun onRunningUseCasesChanged() {}
         }
 
-        val useCaseManager = createUseCaseManager(setOf(fakeControl))
+        val useCaseManager = createUseCaseManager(
+            controls = setOf(fakeControl)
+        )
         val preview = createPreview()
         val useCase = FakeUseCase()
 
@@ -322,6 +411,86 @@
         assertThat(fakeControl.runningUseCases).isEqualTo(setOf(preview, useCase))
     }
 
+    @Test
+    fun useCasesNotifiedOnCameraControlReady_whenAttachingWithSessionProcessor() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val previewUseCase = createFakePreview()
+        val imageCaptureUseCase = createFakeImageCapture()
+
+        val fakeSessionProcessor: SessionProcessor = FakeSessionProcessor()
+
+        useCaseManager.sessionProcessor = fakeSessionProcessor
+        useCaseManager.activate(previewUseCase)
+        useCaseManager.activate(imageCaptureUseCase)
+        useCaseManager.attach(listOf(previewUseCase, imageCaptureUseCase))
+        advanceUntilIdle()
+
+        assertNotNull(useCaseManager.camera)
+        assertThat(useCaseManager.camera!!.runningUseCases).containsExactly(
+            previewUseCase,
+            imageCaptureUseCase
+        )
+        assertTrue(previewUseCase.cameraControlReady)
+        assertTrue(imageCaptureUseCase.cameraControlReady)
+    }
+
+    @Test
+    fun allUseCasesNotifiedOnCameraControlReady_whenSessionProcessorPending() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val previewDeferrableSurface = createBlockingTestDeferrableSurface(Preview::class.java)
+        val imageCaptureDeferrableSurface =
+            createBlockingTestDeferrableSurface(ImageCapture::class.java)
+        val imageAnalysisDeferrableSurface =
+            createBlockingTestDeferrableSurface(ImageAnalysis::class.java)
+        val previewUseCase = createFakePreview(previewDeferrableSurface)
+        val imageCaptureUseCase = createFakeImageCapture(imageCaptureDeferrableSurface)
+        val imageAnalysisUseCase = createFakeImageAnalysis(imageAnalysisDeferrableSurface)
+        val fakeSessionProcessor = FakeSessionProcessor()
+
+        // Act
+        useCaseManager.sessionProcessor = fakeSessionProcessor
+        useCaseManager.activate(previewUseCase)
+        useCaseManager.activate(imageCaptureUseCase)
+        useCaseManager.attach(listOf(previewUseCase, imageCaptureUseCase))
+        advanceUntilIdle()
+        // Here SessionProcessorProcessor.initialize due to not getting its Surfaces. While we're
+        // still initializing, the current UseCaseCamera should be null (i.e., no attached or
+        // running use cases).
+        // Assert
+        assertNull(useCaseManager.camera)
+        // We haven't finished initialization, and therefore the controls aren't ready.
+        assertFalse(previewUseCase.cameraControlReady)
+        assertFalse(imageCaptureUseCase.cameraControlReady)
+
+        // Attaching an ImageAnalysis use case, which should refresh the attached use cases, and
+        // supersede the current set of use cases.
+        useCaseManager.activate(imageAnalysisUseCase)
+        useCaseManager.attach(listOf(imageAnalysisUseCase))
+        // Resume the DeferrableSurfaces to allow them to be retrieved.
+        previewDeferrableSurface.resume()
+        imageCaptureDeferrableSurface.resume()
+        imageAnalysisDeferrableSurface.resume()
+        advanceUntilIdle()
+
+        // Assert
+        assertNotNull(useCaseManager.camera)
+        // Check that the new set of running use cases is Preview, ImageCapture and ImageAnalysis.
+        assertThat(useCaseManager.camera!!.runningUseCases).containsExactly(
+            previewUseCase,
+            imageCaptureUseCase,
+            imageAnalysisUseCase
+        )
+        // Despite only attaching the ImageAnalysis use case in the prior step. All not-yet-notified
+        // use cases should be notified that their camera controls are ready.
+        assertTrue(previewUseCase.cameraControlReady)
+        assertTrue(imageCaptureUseCase.cameraControlReady)
+        assertTrue(imageAnalysisUseCase.cameraControlReady)
+    }
+
     @OptIn(ExperimentalCamera2Interop::class)
     @Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
     private fun createUseCaseManager(
@@ -345,17 +514,6 @@
             characteristics = characteristicsMap
         )
         val fakeCamera = FakeCamera()
-        val fakeUseCaseThreads = run {
-            val executor = MoreExecutors.directExecutor()
-            val dispatcher = executor.asCoroutineDispatcher()
-            val cameraScope = CoroutineScope(Job() + dispatcher)
-
-            UseCaseThreads(
-                cameraScope,
-                executor,
-                dispatcher,
-            )
-        }
         return UseCaseManager(
             cameraPipe = CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
             cameraConfig = CameraConfig(cameraId),
@@ -369,7 +527,7 @@
             ),
             camera2CameraControl = Camera2CameraControl.create(
                 FakeCamera2CameraControlCompat(),
-                useCaseThreads,
+                checkNotNull(useCaseThreads),
                 ComboRequestListener()
             ),
             cameraStateAdapter = CameraStateAdapter(),
@@ -382,16 +540,84 @@
             displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext()),
             context = ApplicationProvider.getApplicationContext(),
             cameraInfoInternal = { fakeCamera.cameraInfoInternal },
-            useCaseThreads = { fakeUseCaseThreads },
+            useCaseThreads = { useCaseThreads },
         ).also {
             useCaseManagerList.add(it)
         }
     }
 
+    private fun initializeUseCaseThreads(testScope: TestScope) {
+        val dispatcher = StandardTestDispatcher(testScope.testScheduler)
+        useCaseThreads = UseCaseThreads(
+            testScope,
+            dispatcher.asExecutor(),
+            dispatcher,
+        )
+    }
+
+    private fun createFakePreview(customDeferrableSurface: DeferrableSurface? = null) =
+        createFakeTestUseCase(
+            "Preview",
+            CameraDevice.TEMPLATE_PREVIEW,
+            Preview::class.java,
+            customDeferrableSurface,
+        )
+
+    private fun createFakeImageCapture(customDeferrableSurface: DeferrableSurface? = null) =
+        createFakeTestUseCase(
+            "ImageCapture",
+            CameraDevice.TEMPLATE_STILL_CAPTURE,
+            ImageCapture::class.java,
+            customDeferrableSurface,
+        )
+
+    private fun createFakeImageAnalysis(customDeferrableSurface: DeferrableSurface? = null) =
+        createFakeTestUseCase(
+            "ImageAnalysis",
+            CameraDevice.TEMPLATE_PREVIEW,
+            ImageAnalysis::class.java,
+            customDeferrableSurface,
+        )
+
+    private fun <T> createFakeTestUseCase(
+        name: String,
+        template: Int,
+        containerClass: Class<T>,
+        customDeferrableSurface: DeferrableSurface? = null,
+    ): FakeTestUseCase {
+        val deferrableSurface =
+            customDeferrableSurface ?: createTestDeferrableSurface(containerClass)
+        return FakeTestUseCase(
+            FakeUseCaseConfig.Builder().setTargetName(name).useCaseConfig
+        ).apply {
+            setupSessionConfig(
+                SessionConfig.Builder().also { sessionConfigBuilder ->
+                    sessionConfigBuilder.setTemplateType(template)
+                    sessionConfigBuilder.addSurface(deferrableSurface)
+                }
+            )
+        }
+    }
+
+    private fun <T> createTestDeferrableSurface(containerClass: Class<T>): TestDeferrableSurface {
+        return TestDeferrableSurface().apply {
+            setContainerClass(containerClass)
+            terminationFuture.addListener({ cleanUp() }, useCaseThreads.backgroundExecutor)
+        }
+    }
+
+    private fun <T> createBlockingTestDeferrableSurface(containerClass: Class<T>):
+        BlockingTestDeferrableSurface {
+        return BlockingTestDeferrableSurface().apply {
+            setContainerClass(containerClass)
+            terminationFuture.addListener({ cleanUp() }, useCaseThreads.backgroundExecutor)
+        }
+    }
+
     private fun createImageCapture(): ImageCapture =
         ImageCapture.Builder()
-            .setCaptureOptionUnpacker { _, _ -> }
-            .setSessionOptionUnpacker { _, _, _ -> }
+            .setCaptureOptionUnpacker(CameraUseCaseAdapter.DefaultCaptureOptionsUnpacker.INSTANCE)
+            .setSessionOptionUnpacker(CameraUseCaseAdapter.DefaultSessionOptionsUnpacker)
             .build().also {
                 it.simulateActivation()
                 useCaseList.add(it)
@@ -399,8 +625,8 @@
 
     private fun createPreview(): Preview =
         Preview.Builder()
-            .setCaptureOptionUnpacker { _, _ -> }
-            .setSessionOptionUnpacker { _, _, _ -> }
+            .setCaptureOptionUnpacker(CameraUseCaseAdapter.DefaultCaptureOptionsUnpacker.INSTANCE)
+            .setSessionOptionUnpacker(CameraUseCaseAdapter.DefaultSessionOptionsUnpacker)
             .build().apply {
                 setSurfaceProvider(
                     CameraXExecutors.mainThreadExecutor(),
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
index 8249625..0ac0345 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
@@ -96,6 +96,15 @@
         throw NotImplementedError("Not used in testing")
     }
 
+    override suspend fun lock3AForCapture(
+        triggerAf: Boolean,
+        waitForAwb: Boolean,
+        frameLimit: Int,
+        timeLimitNs: Long
+    ): Deferred<Result3A> {
+        throw NotImplementedError("Not used in testing")
+    }
+
     override fun setTorch(torchState: TorchState): Deferred<Result3A> {
         throw NotImplementedError("Not used in testing")
     }
@@ -159,7 +168,7 @@
         throw NotImplementedError("Not used in testing")
     }
 
-    override suspend fun unlock3APostCapture(): Deferred<Result3A> {
+    override suspend fun unlock3APostCapture(cancelAf: Boolean): Deferred<Result3A> {
         throw NotImplementedError("Not used in testing")
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSessionProcessor.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSessionProcessor.kt
new file mode 100644
index 0000000..beff862
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeSessionProcessor.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.camera.camera2.pipe.integration.testing
+
+import android.hardware.camera2.CameraDevice
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.impl.OutputSurfaceConfiguration
+import androidx.camera.core.impl.RequestProcessor
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.SessionProcessor
+import androidx.camera.core.impl.SessionProcessorSurface
+
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class FakeSessionProcessor : SessionProcessor {
+    val previewOutputConfigId = 0
+    val imageCaptureOutputConfigId = 1
+    val imageAnalysisOutputConfigId = 2
+
+    var lastParameters: androidx.camera.core.impl.Config? = null
+    var startCapturesCount = 0
+
+    override fun initSession(
+        cameraInfo: CameraInfo,
+        outputSurfaceConfiguration: OutputSurfaceConfiguration,
+    ): SessionConfig {
+        Log.debug { "$this#initSession" }
+        val previewSurface = SessionProcessorSurface(
+            outputSurfaceConfiguration.previewOutputSurface.surface,
+            previewOutputConfigId
+        ).also {
+            it.setContainerClass(Preview::class.java)
+        }
+        val imageCaptureSurface = SessionProcessorSurface(
+            outputSurfaceConfiguration.imageCaptureOutputSurface.surface,
+            imageCaptureOutputConfigId
+        ).also {
+            it.setContainerClass(ImageCapture::class.java)
+        }
+        val imageAnalysisSurface =
+            outputSurfaceConfiguration.imageAnalysisOutputSurface?.surface?.let { surface ->
+                SessionProcessorSurface(
+                    surface,
+                    imageAnalysisOutputConfigId
+                ).also {
+                    it.setContainerClass(ImageAnalysis::class.java)
+                }
+            }
+        return SessionConfig.Builder().apply {
+            setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+            addSurface(previewSurface)
+            addSurface(imageCaptureSurface)
+            imageAnalysisSurface?.let { addSurface(it) }
+        }.build()
+    }
+
+    override fun deInitSession() {
+        Log.debug { "$this#deInitSession" }
+    }
+
+    override fun setParameters(config: androidx.camera.core.impl.Config) {
+        Log.debug { "$this#setParameters" }
+        lastParameters = config
+    }
+
+    override fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
+        Log.debug { "$this#onCaptureSessionStart" }
+    }
+
+    override fun onCaptureSessionEnd() {
+        Log.debug { "$this#onCaptureSessionEnd" }
+    }
+
+    override fun startRepeating(callback: SessionProcessor.CaptureCallback): Int {
+        Log.debug { "$this#startRepeating" }
+        return 0
+    }
+
+    override fun stopRepeating() {
+        Log.debug { "$this#stopRepeating" }
+    }
+
+    override fun startCapture(
+        postviewEnabled: Boolean,
+        callback: SessionProcessor.CaptureCallback
+    ): Int {
+        Log.debug { "$this#startCapture" }
+        startCapturesCount++
+        return 0
+    }
+
+    override fun abortCapture(captureSequenceId: Int) {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraControls.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraControls.kt
index 09c9c59..59e1fbe 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraControls.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraControls.kt
@@ -31,6 +31,15 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @JvmInline
 value class AfMode(val value: Int) {
+    fun isOn(): Boolean {
+        return value != CameraMetadata.CONTROL_AF_MODE_OFF
+    }
+
+    fun isContinuous(): Boolean {
+        return value == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO ||
+            value == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+    }
+
     companion object {
         val OFF = AfMode(CameraMetadata.CONTROL_AF_MODE_OFF)
         val AUTO = AfMode(CameraMetadata.CONTROL_AF_MODE_AUTO)
@@ -50,6 +59,10 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @JvmInline
 value class AeMode(val value: Int) {
+    fun isOn(): Boolean {
+        return value != CameraMetadata.CONTROL_AE_MODE_OFF
+    }
+
     companion object {
         val OFF = AeMode(CameraMetadata.CONTROL_AE_MODE_OFF)
         val ON = AeMode(CameraMetadata.CONTROL_AE_MODE_ON)
@@ -70,6 +83,10 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @JvmInline
 value class AwbMode(val value: Int) {
+    fun isOn(): Boolean {
+        return value != CameraMetadata.CONTROL_AWB_MODE_OFF
+    }
+
     companion object {
         val OFF = AwbMode(CameraMetadata.CONTROL_AWB_MODE_OFF)
         val AUTO = AwbMode(CameraMetadata.CONTROL_AWB_MODE_AUTO)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 8016c46..954d5ea 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -507,6 +507,7 @@
          *   operation to complete.
          * @param timeLimitNs the maximum time limit in ms we wait before we give up waiting for
          *   this operation to complete.
+         *
          * @return [Result3A], which will contain the latest frame number at which the locks were
          *   applied or the frame number at which the method returned early because either frame
          *   limit or time limit was reached.
@@ -514,7 +515,33 @@
         suspend fun lock3AForCapture(
             lockedCondition: ((FrameMetadata) -> Boolean)? = null,
             frameLimit: Int = DEFAULT_FRAME_LIMIT,
-            timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS
+            timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
+        ): Deferred<Result3A>
+
+        /**
+         * This methods does pre-capture metering sequence and locks auto-focus. Once the operation
+         * completes, we can proceed to take high-quality pictures.
+         *
+         * Note: Flash will be used during pre-capture metering and during image capture if the AE
+         * mode was set to [AeMode.ON_AUTO_FLASH] or [AeMode.ON_ALWAYS_FLASH], thus firing it for
+         * low light captures or for every capture, respectively.
+         *
+         * @param triggerAf Whether to trigger AF, enabled by default.
+         * @param waitForAwb Whether to wait for AWB to converge/lock, disabled by default.
+         * @param frameLimit the maximum number of frames to wait before we give up waiting for this
+         *   operation to complete.
+         * @param timeLimitNs the maximum time limit in ms we wait before we give up waiting for
+         *   this operation to complete.
+         *
+         * @return [Result3A], which will contain the latest frame number at which the locks were
+         *   applied or the frame number at which the method returned early because either frame
+         *   limit or time limit was reached.
+         */
+        suspend fun lock3AForCapture(
+            triggerAf: Boolean = true,
+            waitForAwb: Boolean = false,
+            frameLimit: Int = DEFAULT_FRAME_LIMIT,
+            timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
         ): Deferred<Result3A>
 
         /**
@@ -523,8 +550,10 @@
          * capture, and if not image capture request is submitted the auto-exposure may not resume
          * it's normal scan. This method brings focus and exposure back to normal after high quality
          * image captures using [lock3AForCapture] method.
+         *
+         * @param cancelAf  Whether to trigger AF cancel, enabled by default.
          */
-        suspend fun unlock3APostCapture(): Deferred<Result3A>
+        suspend fun unlock3APostCapture(cancelAf: Boolean = true): Deferred<Result3A>
     }
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
index 9d21ba8..79001b0 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
@@ -313,6 +313,12 @@
                 }
             }
             Debug.traceStop()
+        } else {
+            // We still need to indicate the stop signal because the graph state would transition to
+            // GraphStateStarting when the graph is being started.
+            Debug.traceStart { "$graphListener#onGraphStopped" }
+            graphListener.onGraphStopped(null)
+            Debug.traceStop()
         }
 
         var shouldFinalizeSession = false
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
index 1929570..3322589 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
@@ -65,7 +65,7 @@
 internal object NoOpGraphListener : GraphListener {
     override fun onGraphStarted(requestProcessor: GraphRequestProcessor) {}
 
-    override fun onGraphStopped(requestProcessor: GraphRequestProcessor) {}
+    override fun onGraphStopped(requestProcessor: GraphRequestProcessor?) {}
 
     override fun onGraphModified(requestProcessor: GraphRequestProcessor) {}
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
index e60758c..9433599 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
@@ -182,12 +182,31 @@
         timeLimitNs: Long
     ): Deferred<Result3A> {
         check(!closed.value) { "Cannot call lock3AForCapture on $this after close." }
-        return controller3A.lock3AForCapture(lockedCondition, frameLimit, timeLimitNs)
+        return controller3A.lock3AForCapture(
+            lockedCondition,
+            frameLimit,
+            timeLimitNs
+        )
     }
 
-    override suspend fun unlock3APostCapture(): Deferred<Result3A> {
+    override suspend fun lock3AForCapture(
+        triggerAf: Boolean,
+        waitForAwb: Boolean,
+        frameLimit: Int,
+        timeLimitNs: Long
+    ): Deferred<Result3A> {
+        check(!closed.value) { "Cannot call lock3AForCapture on $this after close." }
+        return controller3A.lock3AForCapture(
+            triggerAf,
+            waitForAwb,
+            frameLimit,
+            timeLimitNs
+        )
+    }
+
+    override suspend fun unlock3APostCapture(cancelAf: Boolean): Deferred<Result3A> {
         check(!closed.value) { "Cannot call unlock3APostCapture on $this after close." }
-        return controller3A.unlock3APostCapture()
+        return controller3A.unlock3APostCapture(cancelAf)
     }
 
     override fun toString(): String = "CameraGraph.Session-$debugId"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
index 7cf11a9..83dda3b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
@@ -18,7 +18,10 @@
 
 package androidx.camera.camera2.pipe.graph
 
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF
+import android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_OFF
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_LOCK
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
@@ -93,12 +96,23 @@
                 CaptureResult.CONTROL_AE_STATE_LOCKED
             )
 
+        private val awbPostPrecaptureStateList =
+            listOf(
+                CaptureResult.CONTROL_AWB_STATE_CONVERGED,
+                CaptureResult.CONTROL_AWB_STATE_LOCKED
+            )
+
         val parameterForAfTriggerStart =
             mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START)
 
         val parameterForAfTriggerCancel =
             mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL)
 
+        private val parametersForAePrecapture =
+            mapOf<CaptureRequest.Key<*>, Any>(
+                CONTROL_AE_PRECAPTURE_TRIGGER to CONTROL_AE_PRECAPTURE_TRIGGER_START
+            )
+
         private val parametersForAePrecaptureAndAfTrigger =
             mapOf<CaptureRequest.Key<*>, Any>(
                 CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START,
@@ -132,17 +146,18 @@
                 CaptureResult.CONTROL_AWB_STATE_CONVERGED
             )
 
-        private val defaultLock3AForCaptureLockCondition = mapOf<CaptureResult.Key<*>, List<Any>>(
-            CaptureResult.CONTROL_AE_STATE to aePostPrecaptureStateList,
-            CaptureResult.CONTROL_AF_STATE to afLockedStateList
-        ).toConditionChecker()
+        private val unlock3APostCaptureLockAeParams = mapOf(CONTROL_AE_LOCK to true)
 
-        private val unlock3APostCaptureLockAeParams =
+        private val unlock3APostCaptureLockAeAndCancelAfParams =
             mapOf(CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL, CONTROL_AE_LOCK to true)
 
         private val unlock3APostCaptureUnlockAeParams =
             mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false)
 
+        private val aePrecaptureCancelParams = mapOf<CaptureRequest.Key<*>, Any>(
+            CONTROL_AE_PRECAPTURE_TRIGGER to CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
+        )
+
         private val aePrecaptureAndAfCancelParams = mapOf<CaptureRequest.Key<*>, Any>(
             CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL,
             CONTROL_AE_PRECAPTURE_TRIGGER to CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
@@ -416,25 +431,129 @@
         return listener.result
     }
 
+    /**
+     * Triggers 3A state updates and waits for locking/convergence for high quality image capture.
+     *
+     * By default, both AE precapture and AF are triggered, however this method does not try to lock
+     * the AE/AWB explicitly. So AE/AWB states will reach up to converged state only, not locked
+     * state (unless they were already locked). Use the [lock3A] method afterwards if locking AE/AWB
+     * is also required.
+     *
+     * The exit condition of the API can be customized with [lockedCondition] parameter. See
+     * `lock3AForCapture(triggerCondition, lockedCondition, frameLimit, timeLimitNs)` for details.
+     *
+     * @param lockedCondition Optional customized exit condition for the result.
+     *
+     * @return A [Deferred] containing a [Result3A] which will contain the latest frame number at
+     *  which the locks were applied or the frame number at which the method returned early because
+     *  either frame limit or time limit was reached.
+     */
     suspend fun lock3AForCapture(
         lockedCondition: ((FrameMetadata) -> Boolean)? = null,
         frameLimit: Int = DEFAULT_FRAME_LIMIT,
-        timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS
+        timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
+    ) = lock3AForCapture(
+        triggerCondition = null,
+        lockedCondition = lockedCondition,
+        frameLimit = frameLimit,
+        timeLimitNs = timeLimitNs,
+    )
+
+    /**
+     * Triggers 3A state updates and waits for locking/convergence for high quality image capture.
+     *
+     * By default, both AE precapture and AF are triggered, however this method does not try to lock
+     * the AE/AWB explicitly. So AE/AWB states will reach up to converged state only, not locked
+     * state (unless they were already locked). Use the [lock3A] method afterwards if locking AE/AWB
+     * is also required.
+     *
+     * It is possible to not trigger AF explicitly by disabling the [triggerAf] parameter. However,
+     * if [the AF mode is [CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE] or
+     * [CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO], the AF algorithm will continuously scan for
+     * good focus state even without an explicit AF trigger and may have been effected by the AE
+     * precapture trigger or scenery change. So, this method will still wait for AF to be converged
+     * for these AF modes even if the AF trigger is disabled.
+     *
+     * @param triggerAf     Whether to trigger AF.
+     * @param waitForAwb    Whether to wait for AWB to converge/lock.
+     *
+     * @return A [Deferred] containing a [Result3A] which will contain the latest frame number at
+     *  which the locks were applied or the frame number at which the method returned early because
+     *  either frame limit or time limit was reached.
+     */
+    suspend fun lock3AForCapture(
+        triggerAf: Boolean = true,
+        waitForAwb: Boolean = false,
+        frameLimit: Int = DEFAULT_FRAME_LIMIT,
+        timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
+    ): Deferred<Result3A> {
+        val triggerCondition = if (triggerAf) {
+            parametersForAePrecaptureAndAfTrigger
+        } else {
+            parametersForAePrecapture
+        }
+
+        return lock3AForCapture(
+            triggerCondition = triggerCondition,
+            lockedCondition = createLock3AForCaptureExitConditions(
+                isAfTriggered = triggerAf,
+                waitForAwb = waitForAwb
+            ),
+            frameLimit = frameLimit,
+            timeLimitNs = timeLimitNs,
+        )
+    }
+
+    /**
+     * Triggers 3A state updates and waits for locking/convergence for high quality image capture.
+     *
+     * By default, both AE precapture and AF are triggered, however this method does not try to lock
+     * the AE/AWB explicitly. So AE/AWB states will reach up to converged state only, not locked
+     * state (unless they were already locked). Use the [lock3A] method afterwards if locking AE/AWB
+     * is also required.
+     *
+     * @param triggerCondition  Customized trigger condition. If not provided, both AE precapture
+     *                          and AF.
+     * @param lockedCondition   Customized exit condition for the result. If not provided,
+     *                          `createLock3AForCaptureExitConditions(isAfTriggered = true,
+     *                          waitForAwb = false)` will be used for default condition.
+     *
+     * @return A [Deferred] containing a [Result3A] which will contain the latest frame number at
+     *  which the locks were applied or the frame number at which the method returned early because
+     *  either frame limit or time limit was reached.
+     */
+    private suspend fun lock3AForCapture(
+        triggerCondition: Map<CaptureRequest.Key<*>, Any>? = null,
+        lockedCondition: ((FrameMetadata) -> Boolean)? = null,
+        frameLimit: Int = DEFAULT_FRAME_LIMIT,
+        timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
     ): Deferred<Result3A> {
         // If the GraphProcessor does not have a repeating request, we should fail immediately.
         if (!graphProcessor.hasRepeatingRequest()) {
             return deferredResult3ASubmitFailed
         }
-        val listener =
-            Result3AStateListenerImpl(
-                lockedCondition ?: defaultLock3AForCaptureLockCondition,
-                frameLimit,
-                timeLimitNs
-            )
+
+        val finalTriggerCondition = triggerCondition ?: parametersForAePrecaptureAndAfTrigger
+        var isAfTriggered = false
+        finalTriggerCondition.forEach { entry ->
+            if (entry.value == CONTROL_AE_PRECAPTURE_TRIGGER_START) {
+                isAfTriggered = true
+            }
+        }
+
+        val listener = Result3AStateListenerImpl(
+            lockedCondition ?: createLock3AForCaptureExitConditions(
+                isAfTriggered = isAfTriggered,
+                waitForAwb = false, // no need to wait for AWB in default case
+            ),
+            frameLimit,
+            timeLimitNs
+        )
+
         graphListener3A.addListener(listener)
 
         debug { "lock3AForCapture - sending a request to trigger ae precapture metering and af." }
-        if (!graphProcessor.trySubmit(parametersForAePrecaptureAndAfTrigger)) {
+        if (!graphProcessor.trySubmit(finalTriggerCondition)) {
             debug {
                 "lock3AForCapture - request to trigger ae precapture metering and af failed, " +
                     "returning early."
@@ -447,15 +566,15 @@
         return listener.result
     }
 
-    suspend fun unlock3APostCapture(): Deferred<Result3A> {
+    suspend fun unlock3APostCapture(cancelAf: Boolean = true): Deferred<Result3A> {
         // If the GraphProcessor does not have a repeating request, we should fail immediately.
         if (!graphProcessor.hasRepeatingRequest()) {
             return deferredResult3ASubmitFailed
         }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            return unlock3APostCaptureAndroidMAndAbove()
+            return unlock3APostCaptureAndroidMAndAbove(cancelAf)
         }
-        return unlock3APostCaptureAndroidLAndBelow()
+        return unlock3APostCaptureAndroidLAndBelow(cancelAf)
     }
 
     /**
@@ -464,9 +583,17 @@
      * REF :
      * https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
      */
-    private suspend fun unlock3APostCaptureAndroidLAndBelow(): Deferred<Result3A> {
+    private suspend fun unlock3APostCaptureAndroidLAndBelow(
+        cancelAf: Boolean = true
+    ): Deferred<Result3A> {
         debug { "unlock3AForCapture - sending a request to cancel af and turn on ae." }
-        if (!graphProcessor.trySubmit(unlock3APostCaptureLockAeParams)) {
+        if (!graphProcessor.trySubmit(
+                if (cancelAf) {
+                    unlock3APostCaptureLockAeAndCancelAfParams
+                } else {
+                    unlock3APostCaptureLockAeParams
+                }
+        )) {
             debug { "unlock3AForCapture - request to cancel af and lock ae as failed." }
             return deferredResult3ASubmitFailed
         }
@@ -492,9 +619,12 @@
      * https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
      */
     @RequiresApi(23)
-    private suspend fun unlock3APostCaptureAndroidMAndAbove(): Deferred<Result3A> {
+    private suspend fun unlock3APostCaptureAndroidMAndAbove(
+        cancelAf: Boolean = true
+    ): Deferred<Result3A> {
         debug { "unlock3APostCapture - sending a request to reset af and ae precapture metering." }
-        if (!graphProcessor.trySubmit(aePrecaptureAndAfCancelParams)) {
+        val cancelParams = if (cancelAf) aePrecaptureAndAfCancelParams else aePrecaptureCancelParams
+        if (!graphProcessor.trySubmit(cancelParams)) {
             debug {
                 "unlock3APostCapture - request to reset af and ae precapture metering failed, " +
                     "returning early."
@@ -506,7 +636,11 @@
         // on the ae state, so we don't need to listen for a specific state. As long as the request
         // successfully reaches the camera device and the capture request corresponding to that
         // request arrives back, it should suffice.
-        val listener = Result3AStateListenerImpl(unlock3APostCaptureAfUnlockedCondition)
+        val listener = if (cancelAf) {
+            Result3AStateListenerImpl(unlock3APostCaptureAfUnlockedCondition)
+        } else {
+            Result3AStateListenerImpl(emptyMap())
+        }
         graphListener3A.addListener(listener)
         graphProcessor.invalidate()
         return listener.result
@@ -630,6 +764,47 @@
         return exitConditionMapForLocked
     }
 
+    private fun createLock3AForCaptureExitConditions(
+        isAfTriggered: Boolean,
+        waitForAwb: Boolean,
+    ): ((FrameMetadata) -> Boolean) = { frameMetadata ->
+        val afMode = AfMode(frameMetadata[CaptureResult.CONTROL_AF_MODE] ?: CONTROL_AF_MODE_OFF)
+        val meetsAfCondition = if (afMode.isOn()) {
+            if (isAfTriggered) {
+                afLockedStateList.contains(frameMetadata[CaptureResult.CONTROL_AF_STATE])
+                frameMetadata[CaptureResult.CONTROL_AF_STATE].isNullOrIn(afLockedStateList)
+            } else if (afMode.isContinuous()) {
+                // Even if AF is not triggered, we can still wait for PASSIVE_FOCUS in this case
+                afConvergedStateList.contains(frameMetadata[CaptureResult.CONTROL_AF_STATE])
+            } else {
+                true
+            }
+        } else {
+            true
+        }
+
+        // AE/AWB state may be null in some devices and thus should not be waited for in such case
+
+        val aeMode = AeMode(frameMetadata[CaptureResult.CONTROL_AE_MODE] ?: CONTROL_AE_MODE_OFF)
+        val meetsAeCondition = if (aeMode.isOn()) {
+            frameMetadata[CaptureResult.CONTROL_AE_STATE].isNullOrIn(aePostPrecaptureStateList)
+        } else {
+            true
+        }
+
+        val awbMode = AwbMode(frameMetadata[CaptureResult.CONTROL_AWB_MODE] ?: CONTROL_AWB_MODE_OFF)
+        val meetsAwbCondition = if (awbMode.isOn() && waitForAwb) {
+            frameMetadata[CaptureResult.CONTROL_AWB_STATE].isNullOrIn(awbPostPrecaptureStateList)
+        } else {
+            true
+        }
+
+        debug { "lock3AForCapture result: meetsAeCondition = $meetsAeCondition" +
+            ", meetsAfCondition = $meetsAfCondition, meetsAwbCondition = $meetsAwbCondition" }
+
+        meetsAeCondition && meetsAfCondition && meetsAwbCondition
+    }
+
     private fun createUnLocked3AExitConditions(
         ae: Boolean,
         af: Boolean,
@@ -673,6 +848,10 @@
     }
 }
 
+/** Returns true if this is null or exists in the provided collection. */
+private fun <T> T?.isNullOrIn(collection: Collection<T>) =
+    this?.let { collection.contains(it) } ?: true
+
 internal fun Lock3ABehavior?.shouldUnlockAe(): Boolean = this == Lock3ABehavior.AFTER_NEW_SCAN
 
 internal fun Lock3ABehavior?.shouldUnlockAf(): Boolean = this == Lock3ABehavior.AFTER_NEW_SCAN
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
index 7e8ed2d..3b3a722 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
@@ -44,7 +44,7 @@
      * Used to indicate that a previously initialized [GraphRequestProcessor] is no longer
      * available.
      */
-    fun onGraphStopped(requestProcessor: GraphRequestProcessor)
+    fun onGraphStopped(requestProcessor: GraphRequestProcessor?)
 
     /**
      * Used to indicate that the internal state of the [GraphRequestProcessor] has changed. This is
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
index b5e3182..fc67120 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
@@ -226,9 +226,10 @@
         _graphState.value = GraphStateStopping
     }
 
-    override fun onGraphStopped(requestProcessor: GraphRequestProcessor) {
+    override fun onGraphStopped(requestProcessor: GraphRequestProcessor?) {
         debug { "$this onGraphStopped" }
         _graphState.value = GraphStateStopped
+        if (requestProcessor == null) return
         var old: GraphRequestProcessor? = null
         synchronized(lock) {
             if (closed) {
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionStateTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionStateTest.kt
index eafc0ae..9ccb72c 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionStateTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionStateTest.kt
@@ -38,11 +38,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.isNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyNoInteractions
 import org.robolectric.annotation.Config
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -107,7 +107,7 @@
 
         // And a captureSession is never created
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
     }
 
     @Test
@@ -133,7 +133,7 @@
 
         // Then fakeSurfaceListener marks surfaces as inactive.
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface1))
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface2))
     }
@@ -167,7 +167,7 @@
 
         // Then fakeSurfaceListener does not mark surfaces as inactive.
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
         verify(fakeSurfaceListener, never()).onSurfaceInactive(eq(surface1))
         verify(fakeSurfaceListener, never()).onSurfaceInactive(eq(surface2))
     }
@@ -191,7 +191,7 @@
 
         // Then fakeSurfaceListener marks surfaces as inactive.
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface1))
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface2))
     }
@@ -215,7 +215,7 @@
 
         // Then fakeSurfaceListener marks surfaces as inactive.
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface1))
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface2))
     }
@@ -239,7 +239,7 @@
 
         // Then fakeSurfaceListener marks surfaces as inactive.
         advanceUntilIdle()
-        verifyNoInteractions(fakeGraphListener)
+        verify(fakeGraphListener, times(1)).onGraphStopped(isNull())
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface1))
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(surface2))
     }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AForCaptureTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AForCaptureTest.kt
index 95006b2..de59848 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AForCaptureTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AForCaptureTest.kt
@@ -15,12 +15,14 @@
  */
 
 @file:OptIn(ExperimentalCoroutinesApi::class)
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 
 package androidx.camera.camera2.pipe.graph
 
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.FrameMetadata
 import androidx.camera.camera2.pipe.FrameNumber
 import androidx.camera.camera2.pipe.RequestNumber
@@ -73,29 +75,24 @@
     }
 
     @Test
-    fun testLock3AForCapture() = runTest {
+    fun testLock3AForCapture_when3aModesOn() = runTest {
         val result = controller3A.lock3AForCapture()
         assertThat(result.isCompleted).isFalse()
 
+        val on3aModesResultMetadata = mapOf(
+            CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+            CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_ON,
+            CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_AUTO,
+        )
+
         // Since requirement is to trigger both AF and AE precapture metering. The result of
         // lock3AForCapture call will complete once AE and AF have reached their desired states. In
         // this response i.e cameraResponse1, AF is still scanning so the result won't be complete.
         val cameraResponse = async {
-            listener3A.onRequestSequenceCreated(
-                FakeRequestMetadata(requestNumber = RequestNumber(1))
-            )
-            listener3A.onPartialCaptureResult(
-                FakeRequestMetadata(requestNumber = RequestNumber(1)),
-                FrameNumber(101L),
-                FakeFrameMetadata(
-                    frameNumber = FrameNumber(101L),
-                    resultMetadata =
-                    mapOf(
-                        CaptureResult.CONTROL_AF_STATE to
-                            CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
-                        CaptureResult.CONTROL_AE_STATE to
-                            CaptureResult.CONTROL_AE_STATE_SEARCHING
-                    )
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_SEARCHING
                 )
             )
         }
@@ -106,21 +103,10 @@
         // One we are notified that the AE and AF are in the desired states, the result of
         // lock3AForCapture call will complete.
         launch {
-            listener3A.onRequestSequenceCreated(
-                FakeRequestMetadata(requestNumber = RequestNumber(1))
-            )
-            listener3A.onPartialCaptureResult(
-                FakeRequestMetadata(requestNumber = RequestNumber(1)),
-                FrameNumber(101L),
-                FakeFrameMetadata(
-                    frameNumber = FrameNumber(101L),
-                    resultMetadata =
-                    mapOf(
-                        CaptureResult.CONTROL_AF_STATE to
-                            CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
-                        CaptureResult.CONTROL_AE_STATE to
-                            CaptureResult.CONTROL_AE_STATE_CONVERGED
-                    )
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
                 )
             )
         }
@@ -131,11 +117,175 @@
 
         // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
         // There should be a request to trigger AF and AE precapture metering.
-        val request1 = captureSequenceProcessor.nextEvent().requestSequence
-        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER])
-            .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START)
-        assertThat(request1.requiredParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
-            .isEqualTo(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+        assertCorrectCaptureSequenceInLock3AForCapture()
+    }
+
+    @Test
+    fun testLock3AForCapture_whenWaitingForAwb() = runTest {
+        val result = controller3A.lock3AForCapture(waitForAwb = true)
+        assertThat(result.isCompleted).isFalse()
+
+        val on3aModesResultMetadata = mapOf(
+            CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+            CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_ON,
+            CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_AUTO,
+        )
+
+        // AF/AE completed, but AWB still ongoing so result will be incomplete
+        val cameraResponse = async {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED,
+                    CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_SEARCHING,
+                )
+            )
+        }
+
+        cameraResponse.await()
+        assertThat(result.isCompleted).isFalse()
+
+        // One we are notified that the AE and AF are in the desired states, the result of
+        // lock3AForCapture call will complete.
+        launch {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED,
+                    CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_CONVERGED,
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameMetadata!!.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Result3A.Status.OK)
+
+        // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
+        // There should be a request to trigger AF and AE precapture metering.
+        assertCorrectCaptureSequenceInLock3AForCapture()
+    }
+
+    @Test
+    fun testLock3AForCapture_when3aModesAreOff() = runTest {
+        val result = controller3A.lock3AForCapture()
+        assertThat(result.isCompleted).isFalse()
+
+        val off3aModesResultMetadata = mapOf(
+            CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_OFF,
+            CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_OFF,
+            CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_OFF,
+        )
+
+        // Since the 3A modes are off, the result of lock3AForCapture call will complete without
+        // waiting to be converged.
+        launch {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = off3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to
+                        CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+                    CaptureResult.CONTROL_AE_STATE to
+                        CaptureResult.CONTROL_AE_STATE_SEARCHING
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result.isCompleted).isTrue()
+
+        assertThat(result3A.frameMetadata!!.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Result3A.Status.OK)
+
+        // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
+        // There should be a request to trigger AF and AE precapture metering.
+        assertCorrectCaptureSequenceInLock3AForCapture()
+    }
+
+    @Test
+    fun testLock3AForCapture_withoutAfTrigger_whenAfModeContinuousPicture() = runTest {
+        val result = controller3A.lock3AForCapture(triggerAf = false)
+        assertThat(result.isCompleted).isFalse()
+
+        val on3aModesResultMetadata = mapOf(
+            CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+            CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_ON,
+            CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_AUTO,
+        )
+
+        // In this response, AF is still scanning so the result won't be complete.
+        val cameraResponse = async {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN
+                )
+            )
+        }
+
+        cameraResponse.await()
+        assertThat(result.isCompleted).isFalse()
+
+        // One we are notified that AF and AE are in the desired states, the result will complete.
+        launch {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to
+                        CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameMetadata!!.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Result3A.Status.OK)
+
+        // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
+        // There should be a request to trigger AE precapture metering, but not AF.
+        assertCorrectCaptureSequenceInLock3AForCapture(false)
+    }
+
+    @Test
+    fun testLock3AForCapture_withoutAfTrigger_whenAfModeAuto() = runTest {
+        val result = controller3A.lock3AForCapture(triggerAf = false)
+        assertThat(result.isCompleted).isFalse()
+
+        val on3aModesResultMetadata = mapOf(
+            CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO,
+            CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_ON,
+            CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_AUTO,
+        )
+
+        // In this response, AF is still scanning so the result won't be complete.
+        val cameraResponse = async {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_INACTIVE,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_SEARCHING
+                )
+            )
+        }
+
+        cameraResponse.await()
+        assertThat(result.isCompleted).isFalse()
+
+        // Since AF mode is AUTO and AF is not triggered, the result will complete without AF
+        // convergence.
+        launch {
+            listener3A.sendPartialCaptureResult(
+                resultMetadata = on3aModesResultMetadata + mapOf(
+                    CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_INACTIVE,
+                    CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameMetadata!!.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Result3A.Status.OK)
+
+        // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
+        // There should be a request to trigger AE precapture metering, but not AF.
+        assertCorrectCaptureSequenceInLock3AForCapture(false)
     }
 
     @Test
@@ -218,12 +368,21 @@
         }
     }
 
-    private fun testUnlock3APostCaptureAndroidMAndAbove() = runTest {
-        val result = controller3A.unlock3APostCapture()
+    @Test
+    fun testUnlock3APostCapture_whenAfNotTriggered() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            testUnlock3APostCaptureAndroidMAndAbove(false)
+        } else {
+            testUnlock3APostCaptureAndroidLAndBelow(false)
+        }
+    }
+
+    private fun testUnlock3APostCaptureAndroidMAndAbove(cancelAf: Boolean = true) = runTest {
+        val result = controller3A.unlock3APostCapture(cancelAf)
         assertThat(result.isCompleted).isFalse()
 
         // In this response i.e cameraResponse1, AF is still scanning so the result won't be
-        // complete.
+        // complete if AF cancellation is required.
         val cameraResponse = async {
             listener3A.onRequestSequenceCreated(
                 FakeRequestMetadata(requestNumber = RequestNumber(1))
@@ -245,7 +404,11 @@
         }
 
         cameraResponse.await()
-        assertThat(result.isCompleted).isFalse()
+        if (cancelAf) {
+            assertThat(result.isCompleted).isFalse()
+        } else {
+            assertThat(result.isCompleted).isTrue()
+        }
 
         // Once we are notified that the AF is in unlocked state, the result of unlock3APostCapture
         // call will complete. For AE we don't need to to check for a specific state, receiving the
@@ -277,14 +440,16 @@
         // We now check if the correct sequence of requests were submitted by unlock3APostCapture
         // call. There should be a request to cancel AF and AE precapture metering.
         val request1 = captureSequenceProcessor.nextEvent().requestSequence
-        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER])
-            .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
-        assertThat(request1.requiredParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
+        if (cancelAf) {
+            assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER])
+                .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
+        }
+        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
             .isEqualTo(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL)
     }
 
-    private fun testUnlock3APostCaptureAndroidLAndBelow() = runTest {
-        val result = controller3A.unlock3APostCapture()
+    private fun testUnlock3APostCaptureAndroidLAndBelow(cancelAf: Boolean = true) = runTest {
+        val result = controller3A.unlock3APostCapture(cancelAf)
         assertThat(result.isCompleted).isFalse()
 
         val cameraResponse = async {
@@ -306,17 +471,52 @@
         // We now check if the correct sequence of requests were submitted by unlock3APostCapture
         // call. There should be a request to cancel AF and lock ae.
         val request1 = captureSequenceProcessor.nextEvent().requestSequence
-        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER])
-            .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
-        assertThat(request1.requiredParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(true)
+        if (cancelAf) {
+            assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER])
+                .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
+        }
+        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(true)
 
         // Then another request to unlock ae.
         val request2 = captureSequenceProcessor.nextEvent().requestSequence
         assertThat(request2!!.requiredParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(false)
     }
 
+    private suspend fun assertCorrectCaptureSequenceInLock3AForCapture(
+        isAfTriggered: Boolean = true
+    ) {
+        val request1 = captureSequenceProcessor.nextEvent().requestSequence
+        assertThat(request1!!.requiredParameters[CaptureRequest.CONTROL_AF_TRIGGER]).apply {
+            if (isAfTriggered) {
+                isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START)
+            } else {
+                isNotEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_IDLE)
+            }
+        }
+        assertThat(request1.requiredParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
+            .isEqualTo(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+    }
+
     companion object {
         // The time duration in milliseconds between two frame results.
         private const val FRAME_RATE_MS = 33L
     }
 }
+
+private fun Listener3A.sendPartialCaptureResult(
+    requestNumber: Long = 1L,
+    frameNumber: Long = 101L,
+    resultMetadata: Map<CaptureResult.Key<*>, Any?>
+) {
+    onRequestSequenceCreated(
+        FakeRequestMetadata(requestNumber = RequestNumber(requestNumber))
+    )
+    onPartialCaptureResult(
+        FakeRequestMetadata(requestNumber = RequestNumber(requestNumber)),
+        FrameNumber(frameNumber),
+        FakeFrameMetadata(
+            frameNumber = FrameNumber(101L),
+            resultMetadata = resultMetadata
+        )
+    )
+}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
index b32c80d..be0e8eb 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
@@ -129,8 +129,9 @@
         _graphState.value = GraphStateStopping
     }
 
-    override fun onGraphStopped(requestProcessor: GraphRequestProcessor) {
+    override fun onGraphStopped(requestProcessor: GraphRequestProcessor?) {
         _graphState.value = GraphStateStopped
+        if (requestProcessor == null) return
         val old = processor
         if (requestProcessor === old) {
             processor = null
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/DeleteMe.kt b/camera/camera-camera2/src/main/java/androidx/camera/camera2/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index a768062..2a567c8 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -520,7 +520,7 @@
   @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
     method public static long getDefaultRetryTimeoutInMillis();
     method public default long getTimeoutInMillis();
-    method public androidx.camera.core.RetryPolicy.RetryResponse shouldRetry(androidx.camera.core.RetryPolicy.ExecutionState);
+    method public androidx.camera.core.RetryPolicy.RetryConfig onRetryDecisionRequested(androidx.camera.core.RetryPolicy.ExecutionState);
     field public static final androidx.camera.core.RetryPolicy DEFAULT;
     field public static final androidx.camera.core.RetryPolicy NEVER;
     field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
@@ -542,20 +542,20 @@
     field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
   }
 
-  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse {
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig {
     method public static long getDefaultRetryDelayInMillis();
     method public long getRetryDelayInMillis();
     method public boolean shouldRetry();
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse DEFAULT_DELAY_RETRY;
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse MINI_DELAY_RETRY;
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse NOT_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig NOT_RETRY;
   }
 
-  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse.Builder {
-    ctor public RetryPolicy.RetryResponse.Builder();
-    method public androidx.camera.core.RetryPolicy.RetryResponse build();
-    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
-    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setShouldRetry(boolean);
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig.Builder {
+    ctor public RetryPolicy.RetryConfig.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryConfig build();
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setShouldRetry(boolean);
   }
 
   @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index a768062..2a567c8 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -520,7 +520,7 @@
   @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
     method public static long getDefaultRetryTimeoutInMillis();
     method public default long getTimeoutInMillis();
-    method public androidx.camera.core.RetryPolicy.RetryResponse shouldRetry(androidx.camera.core.RetryPolicy.ExecutionState);
+    method public androidx.camera.core.RetryPolicy.RetryConfig onRetryDecisionRequested(androidx.camera.core.RetryPolicy.ExecutionState);
     field public static final androidx.camera.core.RetryPolicy DEFAULT;
     field public static final androidx.camera.core.RetryPolicy NEVER;
     field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
@@ -542,20 +542,20 @@
     field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
   }
 
-  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse {
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig {
     method public static long getDefaultRetryDelayInMillis();
     method public long getRetryDelayInMillis();
     method public boolean shouldRetry();
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse DEFAULT_DELAY_RETRY;
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse MINI_DELAY_RETRY;
-    field public static final androidx.camera.core.RetryPolicy.RetryResponse NOT_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig NOT_RETRY;
   }
 
-  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse.Builder {
-    ctor public RetryPolicy.RetryResponse.Builder();
-    method public androidx.camera.core.RetryPolicy.RetryResponse build();
-    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
-    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setShouldRetry(boolean);
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig.Builder {
+    ctor public RetryPolicy.RetryConfig.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryConfig build();
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setShouldRetry(boolean);
   }
 
   @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 9401862..c3c39aa 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -350,20 +350,20 @@
                 completer.set(null);
             } catch (CameraIdListIncorrectException | InitializationException
                      | RuntimeException e) {
-                RetryPolicy.RetryResponse response = mRetryPolicy.shouldRetry(
+                RetryPolicy.RetryConfig retryConfig = mRetryPolicy.onRetryDecisionRequested(
                         new CameraProviderExecutionState(startMs, attemptCount, e));
-                if (response.shouldRetry() && attemptCount < Integer.MAX_VALUE) {
+                if (retryConfig.shouldRetry() && attemptCount < Integer.MAX_VALUE) {
                     Logger.w(TAG, "Retry init. Start time " + startMs + " current time "
                             + SystemClock.elapsedRealtime(), e);
                     HandlerCompat.postDelayed(mSchedulerHandler, () -> initAndRetryRecursively(
                             cameraExecutor, startMs, attemptCount + 1, mAppContext,
-                            completer), RETRY_TOKEN, response.getRetryDelayInMillis());
+                            completer), RETRY_TOKEN, retryConfig.getRetryDelayInMillis());
 
                 } else {
                     synchronized (mInitializeLock) {
                         mInitState = InternalInitState.INITIALIZING_ERROR;
                     }
-                    if (response.shouldCompleteWithoutFailure()) {
+                    if (retryConfig.shouldCompleteWithoutFailure()) {
                         // Ignoring camera failure for compatibility reasons. Initialization will
                         // be marked as complete, but some camera features might be unavailable.
                         setStateToInitialized();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/DeleteMe.kt b/camera/camera-core/src/main/java/androidx/camera/core/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java
index 2fb6980..3756d74 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java
@@ -71,22 +71,22 @@
  *             if (executionState.getExecutedTimeInMillis() > 10000L
  *                     || executionState.getNumOfAttempts() > 10
  *                     || executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) {
- *                 return RetryResponse.NOT_RETRY;
+ *                 return RetryConfig.NOT_RETRY;
  *             } else if (executionState.getStatus() == ExecutionState.STATUS_CAMERA_UNAVAILABLE) {
- *                 return RetryResponse.DEFAULT_DELAY_RETRY;
+ *                 return RetryConfig.DEFAULT_DELAY_RETRY;
  *             } else {
  *                 Log.d("CameraX", "Unknown error occur: " + executionState.getCause());
- *                 return RetryResponse.MINI_DELAY_RETRY;
+ *                 return RetryConfig.MINI_DELAY_RETRY;
  *             }
  *         }).build());
  * ...
  * }</pre>
  * In the second example, the custom retry policy retries the initialization up to 10 times or
  * for a maximum of 10 seconds. If an unknown error occurs, the retry policy delays the next
- * retry after a delay defined by {@link RetryResponse#MINI_DELAY_RETRY}. The retry process
+ * retry after a delay defined by {@link RetryConfig#MINI_DELAY_RETRY}. The retry process
  * stops if the status is {@link ExecutionState#STATUS_CONFIGURATION_FAIL}. For
  * {@link ExecutionState#STATUS_CAMERA_UNAVAILABLE}, the retry policy applies
- * {@link RetryResponse#DEFAULT_DELAY_RETRY}.
+ * {@link RetryConfig#DEFAULT_DELAY_RETRY}.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @ExperimentalRetryPolicy
@@ -103,7 +103,7 @@
      * immediately halts the initialization upon encountering an error.
      */
     @NonNull
-    RetryPolicy NEVER = executionState -> RetryResponse.NOT_RETRY;
+    RetryPolicy NEVER = executionState -> RetryConfig.NOT_RETRY;
 
     /**
      * This retry policy increases initialization success by automatically retrying upon
@@ -154,13 +154,14 @@
     }
 
     /**
-     * Determines whether to retry the initialization.
+     * Called to request a decision on whether to retry the initialization process.
      *
-     * @param executionState The information about the execution state of the camera initialization.
-     * @return A RetryResponse indicating whether to retry the initialization.
+     * @param executionState Information about the current execution state of the camera
+     *                       initialization.
+     * @return A RetryConfig indicating whether to retry, along with any associated delay.
      */
     @NonNull
-    RetryResponse shouldRetry(@NonNull ExecutionState executionState);
+    RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState);
 
     /**
      * Returns the maximum allowed retry duration in milliseconds. Initialization will
@@ -208,7 +209,7 @@
 
         /**
          * Sets a timeout in milliseconds. If retries exceed this duration, they will be
-         * terminated with {@link RetryPolicy.RetryResponse#NOT_RETRY}.
+         * terminated with {@link RetryConfig#NOT_RETRY}.
          *
          * @param timeoutInMillis The maximum duration for retries in milliseconds. A value of 0
          *                        indicates no timeout.
@@ -332,26 +333,26 @@
      * Represents the outcome of a {@link RetryPolicy} decision.
      */
     @ExperimentalRetryPolicy
-    final class RetryResponse {
+    final class RetryConfig {
 
         private static final long MINI_DELAY_MILLIS = 100L;
         private static final long DEFAULT_DELAY_MILLIS = 500L;
 
-        /** A RetryResponse indicating that no further retries should be attempted. */
+        /** A RetryConfig indicating that no further retries should be attempted. */
         @NonNull
-        public static final RetryResponse NOT_RETRY = new RetryResponse(false, 0L);
+        public static final RetryConfig NOT_RETRY = new RetryConfig(false, 0L);
 
         /**
-         * A RetryResponse indicating that the initialization should be retried after the default
+         * A RetryConfig indicating that the initialization should be retried after the default
          * delay (determined by {@link #getDefaultRetryDelayInMillis()}). This delay provides
          * sufficient time for typical device recovery processes, balancing retry efficiency
          * and minimizing user wait time.
          */
         @NonNull
-        public static final RetryResponse DEFAULT_DELAY_RETRY = new RetryResponse(true);
+        public static final RetryConfig DEFAULT_DELAY_RETRY = new RetryConfig(true);
 
         /**
-         * A RetryResponse indicating that the initialization should be retried after a minimum
+         * A RetryConfig indicating that the initialization should be retried after a minimum
          * delay of 100 milliseconds.
          *
          * This short delay serves two purposes:
@@ -365,18 +366,17 @@
          * fastest possible camera restoration.
          */
         @NonNull
-        public static final RetryResponse MINI_DELAY_RETRY = new RetryResponse(true,
-                MINI_DELAY_MILLIS);
+        public static final RetryConfig MINI_DELAY_RETRY = new RetryConfig(true, MINI_DELAY_MILLIS);
 
         /**
-         * A RetryResponse indicating that the initialization should be considered complete
-         * without retrying. This response is intended for internal use and is not intended to
+         * A RetryConfig indicating that the initialization should be considered complete
+         * without retrying. This config is intended for internal use and is not intended to
          * trigger further retries. It represents the legacy behavior of not failing the
          * initialization task for minor issues.
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @NonNull
-        public static RetryResponse COMPLETE_WITHOUT_FAILURE = new RetryResponse(false, 0, true);
+        public static RetryConfig COMPLETE_WITHOUT_FAILURE = new RetryConfig(false, 0, true);
 
         /**
          * Returns the recommended default delay to optimize retry attempts and camera recovery.
@@ -400,11 +400,11 @@
         private final boolean mShouldRetry;
         private final boolean mCompleteWithoutFailure;
 
-        private RetryResponse(boolean shouldRetry) {
-            this(shouldRetry, RetryResponse.getDefaultRetryDelayInMillis());
+        private RetryConfig(boolean shouldRetry) {
+            this(shouldRetry, RetryConfig.getDefaultRetryDelayInMillis());
         }
 
-        private RetryResponse(boolean shouldRetry, long delayInMillis) {
+        private RetryConfig(boolean shouldRetry, long delayInMillis) {
             this(shouldRetry, delayInMillis, false);
         }
 
@@ -420,7 +420,7 @@
          *                               When this flag is set to true, `shouldRetry` must be
          *                               false.
          */
-        private RetryResponse(boolean shouldRetry, long delayInMillis,
+        private RetryConfig(boolean shouldRetry, long delayInMillis,
                 boolean completeWithoutFailure) {
             mShouldRetry = shouldRetry;
             mDelayInMillis = delayInMillis;
@@ -452,7 +452,7 @@
         /**
          * Signals to treat initialization errors as successful for legacy behavior compatibility.
          *
-         * <p>This response is intended for internal use and is not intended to trigger further
+         * <p>This config is intended for internal use and is not intended to trigger further
          * retries.
          *
          * @return true if initialization should be deemed complete without additional retries,
@@ -464,9 +464,9 @@
         }
 
         /**
-         * A builder class for creating and customizing {@link RetryResponse} objects.
+         * A builder class for creating and customizing {@link RetryConfig} objects.
          *
-         * <p>While predefined responses like {@link RetryResponse#DEFAULT_DELAY_RETRY} are
+         * <p>While predefined configs like {@link RetryConfig#DEFAULT_DELAY_RETRY} are
          * recommended for typical recovery scenarios, this builder allows for fine-tuned control
          * when specific requirements necessitate a different approach.
          */
@@ -474,7 +474,7 @@
         public static final class Builder {
 
             private boolean mShouldRetry = true;
-            private long mTimeoutInMillis = RetryResponse.getDefaultRetryDelayInMillis();
+            private long mTimeoutInMillis = RetryConfig.getDefaultRetryDelayInMillis();
 
             /**
              * Specifies whether a retry should be attempted.
@@ -508,13 +508,13 @@
             }
 
             /**
-             * Builds the customized {@link RetryResponse} object.
+             * Builds the customized {@link RetryConfig} object.
              *
-             * @return The configured RetryResponse.
+             * @return The configured RetryConfig.
              */
             @NonNull
-            public RetryResponse build() {
-                return new RetryResponse(mShouldRetry, mTimeoutInMillis);
+            public RetryConfig build() {
+                return new RetryConfig(mShouldRetry, mTimeoutInMillis);
             }
         }
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java
index 1efbe47..2ef47e1 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java
@@ -28,7 +28,7 @@
  * Basic retry policy that automatically retries most failures with a standard delay.
  *
  * <p>This policy will initiate a retry with the
- * {@link RetryResponse#DEFAULT_DELAY_RETRY} delay for any failure status except
+ * {@link RetryConfig#DEFAULT_DELAY_RETRY} delay for any failure status except
  * {@link ExecutionState#STATUS_CONFIGURATION_FAIL}.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@@ -41,12 +41,12 @@
         mDelegatePolicy = new TimeoutRetryPolicy(timeoutInMillis, new RetryPolicy() {
             @NonNull
             @Override
-            public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
+            public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
                 if (executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) {
-                    return RetryResponse.NOT_RETRY;
+                    return RetryConfig.NOT_RETRY;
                 }
 
-                return RetryResponse.DEFAULT_DELAY_RETRY;
+                return RetryConfig.DEFAULT_DELAY_RETRY;
             }
 
             @Override
@@ -58,8 +58,8 @@
 
     @NonNull
     @Override
-    public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
-        return mDelegatePolicy.shouldRetry(executionState);
+    public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
+        return mDelegatePolicy.onRetryDecisionRequested(executionState);
     }
 
     @Override
@@ -99,8 +99,8 @@
 
         @NonNull
         @Override
-        public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
-            if (!mBasePolicy.shouldRetry(executionState).shouldRetry()) {
+        public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
+            if (!mBasePolicy.onRetryDecisionRequested(executionState).shouldRetry()) {
                 Throwable cause = executionState.getCause();
                 if (cause instanceof CameraIdListIncorrectException) {
                     Logger.e("CameraX", "The device might underreport the amount of the "
@@ -110,12 +110,12 @@
                         // If the initialization task execution time exceeds the timeout
                         // threshold and the error type is CameraIdListIncorrectException,
                         // consider the initialization complete without retrying.
-                        return RetryResponse.COMPLETE_WITHOUT_FAILURE;
+                        return RetryConfig.COMPLETE_WITHOUT_FAILURE;
                     }
                 }
-                return RetryResponse.NOT_RETRY;
+                return RetryConfig.NOT_RETRY;
             }
-            return RetryResponse.DEFAULT_DELAY_RETRY;
+            return RetryConfig.DEFAULT_DELAY_RETRY;
         }
 
         @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java
index 139ba82..5145b43 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java
@@ -27,7 +27,7 @@
  *
  * <p>This retry policy monitors the total execution time of a task. If the time surpasses the
  * configured timeout threshold, it immediately stops any further retries by returning
- * {@link RetryPolicy.RetryResponse#NOT_RETRY}.
+ * {@link RetryConfig#NOT_RETRY}.
  *
  * <p>If the task total execution within the timeout, this policy delegates the retry decision to
  * the underlying {@link RetryPolicy}, allowing for normal retry behavior based on other factors.
@@ -56,11 +56,11 @@
 
     @NonNull
     @Override
-    public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
-        RetryResponse response = mDelegatePolicy.shouldRetry(executionState);
+    public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
+        RetryConfig retryConfig = mDelegatePolicy.onRetryDecisionRequested(executionState);
         return getTimeoutInMillis() > 0 && executionState.getExecutedTimeInMillis()
-                >= getTimeoutInMillis() - response.getRetryDelayInMillis() ? RetryResponse.NOT_RETRY
-                : response;
+                >= getTimeoutInMillis() - retryConfig.getRetryDelayInMillis()
+                ? RetryConfig.NOT_RETRY : retryConfig;
     }
 
     @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/resolutionselector/ResolutionSelector.java b/camera/camera-core/src/main/java/androidx/camera/core/resolutionselector/ResolutionSelector.java
index a880482..9e4e6ba 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/resolutionselector/ResolutionSelector.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/resolutionselector/ResolutionSelector.java
@@ -242,6 +242,14 @@
          * Sets the resolution selection strategy for the {@link UseCase}. The resolution selection
          * strategy determines how the {@link UseCase} will choose the resolution of the captured
          * image.
+         *
+         * <p>Note: The default {@link AspectRatioStrategy} is
+         * {@link AspectRatioStrategy#RATIO_4_3_FALLBACK_AUTO_STRATEGY}. Ensure you set a
+         * corresponding {@link AspectRatioStrategy} alongside your {@link ResolutionStrategy}.
+         * For example, if your {@link ResolutionStrategy} uses a bound size of {@code 1920x1080}
+         * and a 16:9 aspect ratio is preferred, set
+         * {@link AspectRatioStrategy#RATIO_16_9_FALLBACK_AUTO_STRATEGY} when building the
+         * {@link ResolutionSelector}.
          */
         @NonNull
         public Builder setResolutionStrategy(@NonNull ResolutionStrategy resolutionStrategy) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt
index 15b652d..1103c00 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt
@@ -27,7 +27,7 @@
 import androidx.camera.core.RetryPolicy.ExecutionState
 import androidx.camera.core.RetryPolicy.NEVER
 import androidx.camera.core.RetryPolicy.RETRY_UNAVAILABLE_CAMERA
-import androidx.camera.core.RetryPolicy.RetryResponse
+import androidx.camera.core.RetryPolicy.RetryConfig
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.impl.CameraDeviceSurfaceManager
 import androidx.camera.core.impl.CameraFactory
@@ -101,7 +101,7 @@
         val executionStateMutableList = mutableListOf<ExecutionState>()
         val policy = RetryPolicy { executionState: ExecutionState ->
             executionStateMutableList.add(executionState)
-            return@RetryPolicy DEFAULT.shouldRetry(executionState)
+            return@RetryPolicy DEFAULT.onRetryDecisionRequested(executionState)
         }
         val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
             createCameraXConfig(
@@ -196,7 +196,7 @@
             setSchedulerHandler(handler)
             setCameraProviderInitRetryPolicy { executionState ->
                 callCount++
-                NEVER.shouldRetry(executionState)
+                NEVER.onRetryDecisionRequested(executionState)
             }
         }
 
@@ -215,7 +215,7 @@
     }
 
     @Test
-    fun verifyImmediateFailureWithOptionResponseNotRetry() = runTest {
+    fun verifyImmediateFailureWithOptionRetryConfigNotRetry() = runTest {
         // Arrange. Set up a simulated environment that no accessible cameras.
         var callCount = 0
         val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
@@ -230,7 +230,7 @@
             setSchedulerHandler(handler)
             setCameraProviderInitRetryPolicy {
                 callCount++
-                RetryResponse.Builder().setShouldRetry(false).build()
+                RetryConfig.Builder().setShouldRetry(false).build()
             }
         }
 
@@ -283,7 +283,7 @@
             setCameraExecutor(handlerExecutor)
             setSchedulerHandler(handler)
             setCameraProviderInitRetryPolicy { executionState: ExecutionState ->
-                RETRY_UNAVAILABLE_CAMERA.shouldRetry(executionState).also {
+                RETRY_UNAVAILABLE_CAMERA.onRetryDecisionRequested(executionState).also {
                     executedTime = executionState.executedTimeInMillis
                 }
             }
@@ -302,7 +302,7 @@
         throwableSubject.hasCauseThat().isInstanceOf(CameraUnavailableException::class.java)
         assertThat(cameraX.isInitialized).isFalse()
         assertThat(abs(DEFAULT_RETRY_TIMEOUT_IN_MILLIS - executedTime)).isLessThan(
-            RetryResponse.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
+            RetryConfig.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
             // Allow the tolerance for retry delay + 100ms potential processing time variations.
         )
     }
@@ -320,7 +320,7 @@
     @Test
     fun testTimeoutAdjustment_CustomRetryPolicyMode() = runTest {
         // Arrange. Set up a RetryPolicy that persistently retries initialization attempts.
-        val customAlwaysRetryPolicy = RetryPolicy { RetryResponse.MINI_DELAY_RETRY }
+        val customAlwaysRetryPolicy = RetryPolicy { RetryConfig.MINI_DELAY_RETRY }
 
         // Act. & Assert. Confirm that retries cease if the total execution time surpasses the
         // defined timeout, preventing indefinite loops.
@@ -339,7 +339,7 @@
             setCameraExecutor(handlerExecutor)
             setSchedulerHandler(handler)
             setCameraProviderInitRetryPolicy { executionState ->
-                customTimeoutPolicy.shouldRetry(executionState).also {
+                customTimeoutPolicy.onRetryDecisionRequested(executionState).also {
                     executedTime = executionState.executedTimeInMillis
                 }
             }
@@ -357,7 +357,7 @@
         // Assert. Verify that initialization persists with retries until the total execution
         // time exhausts the allotted timeout.
         assertThat(abs(testCustomTimeout - executedTime)).isLessThan(
-            RetryResponse.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
+            RetryConfig.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
             // Allow the tolerance for retry delay + 100ms potential processing time variations.
         )
     }
@@ -367,7 +367,7 @@
         val desiredDelayTime = 900L
 
         assertThat(
-            RetryResponse.Builder().setRetryDelayInMillis(desiredDelayTime)
+            RetryConfig.Builder().setRetryDelayInMillis(desiredDelayTime)
                 .build().retryDelayInMillis
         ).isEqualTo(
             desiredDelayTime
@@ -381,12 +381,12 @@
         val timeoutInMs = 10000L
         val executionStateMutableList = mutableListOf<ExecutionState>()
         val policy = object : RetryPolicy {
-            override fun shouldRetry(executionState: ExecutionState): RetryResponse {
+            override fun onRetryDecisionRequested(executionState: ExecutionState): RetryConfig {
                 if (executionState.getExecutedTimeInMillis() < timeoutInMillis) {
                     executionStateMutableList.add(executionState)
                 }
 
-                return RetryResponse.DEFAULT_DELAY_RETRY
+                return RetryConfig.DEFAULT_DELAY_RETRY
             }
 
             override fun getTimeoutInMillis(): Long {
@@ -437,10 +437,10 @@
         val policy = RetryPolicy { executionState ->
             if (executionState.getExecutedTimeInMillis() < timeoutInMs) {
                 executionStateMutableList.add(executionState)
-                return@RetryPolicy RetryResponse.DEFAULT_DELAY_RETRY;
+                return@RetryPolicy RetryConfig.DEFAULT_DELAY_RETRY;
             }
 
-            return@RetryPolicy RetryResponse.NOT_RETRY
+            return@RetryPolicy RetryConfig.NOT_RETRY
         }
         val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
             createCameraXConfig()
@@ -486,10 +486,10 @@
         val policy = RetryPolicy { executionState: ExecutionState ->
             executionStateMutableList.add(executionState)
             if (executionState.numOfAttempts < maxAttempts) {
-                return@RetryPolicy RetryResponse.DEFAULT_DELAY_RETRY;
+                return@RetryPolicy RetryConfig.DEFAULT_DELAY_RETRY;
             }
 
-            return@RetryPolicy RetryResponse.NOT_RETRY
+            return@RetryPolicy RetryConfig.NOT_RETRY
         }
         val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
             createCameraXConfig()
@@ -520,7 +520,7 @@
         val resultList = mutableListOf<ExecutionState>()
         val policy = RetryPolicy { executionState: ExecutionState ->
             resultList.add(executionState)
-            RETRY_UNAVAILABLE_CAMERA.shouldRetry(executionState)
+            RETRY_UNAVAILABLE_CAMERA.onRetryDecisionRequested(executionState)
         }
         val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
             createCameraXConfig(surfaceManager = null, useCaseConfigFactory = null)
@@ -573,7 +573,7 @@
             setSchedulerHandler(handler)
             setCameraProviderInitRetryPolicy { executionState: ExecutionState ->
                 executionStateMutableList.add(executionState)
-                RetryResponse.NOT_RETRY
+                RetryConfig.NOT_RETRY
             }
         }
 
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/DeleteMe.kt b/camera/camera-extensions/src/main/java/androidx/camera/extensions/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/DeleteMe.kt b/camera/camera-video/src/main/java/androidx/camera/video/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-video/src/main/java/androidx/camera/video/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/DeleteMe.kt b/camera/camera-view/src/main/java/androidx/camera/view/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/DeleteMe.kt b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
index 3d89096..38c0edf 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
@@ -165,7 +165,7 @@
     <string name="gas_station" msgid="1203313937444666161">"SPBU"</string>
     <string name="short_route" msgid="4831864276538141265">"Rute pendek"</string>
     <string name="less_busy" msgid="310625272281710983">"Tidak terlalu sibuk"</string>
-    <string name="hov_friendly" msgid="6956152104754594971">"Cocok untuk Kendaraan Padat Penumpang"</string>
+    <string name="hov_friendly" msgid="6956152104754594971">"Sesuai untuk kendaraan multi-penumpang"</string>
     <string name="long_route" msgid="4737969235741057506">"Rute panjang"</string>
     <string name="continue_start_nav" msgid="6231797535084469163">"Lanjutkan untuk memulai navigasi"</string>
     <string name="continue_route" msgid="5172258139245088080">"Lanjutkan ke rute"</string>
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
index 01f91c7..20227ea 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
@@ -1088,6 +1088,40 @@
         "Runtime(A)"
     )
 
+    // b/327643787
+    @Test
+    fun testNestedExternalTypesAreStable() = assertStability(
+        externalSrc = "",
+        localSrc = """
+            data class B(val list: List<Int>)
+            data class A(val list: List<B>)
+        """.trimIndent(),
+        expression = "A(listOf())",
+        externalTypes = setOf("kotlin.collections.List"),
+        stability = "Stable"
+    )
+
+    @Test
+    fun testNestedGenericsAreRuntimeStable() = assertStability(
+        externalSrc = "",
+        localSrc = """
+            class A(val child: List<A>?)
+        """.trimIndent(),
+        expression = "A(null)",
+        externalTypes = setOf("kotlin.collections.List"),
+        stability = "Unstable"
+    )
+
+    @Test
+    fun testNestedEqualTypesAreUnstable() = assertStability(
+        externalSrc = "",
+        localSrc = """
+            class A(val child: A?)
+        """.trimIndent(),
+        expression = "A(A(null))",
+        stability = "Unstable"
+    )
+
     @Test
     fun testEmptyClass() = assertTransform(
         """
@@ -1593,7 +1627,9 @@
         """.trimIndent()
 
         val files = listOf(SourceFile("Test.kt", source))
-        val irModule = compileToIr(files)
+        val irModule = compileToIr(files, registerExtensions = {
+            it.put(ComposeConfiguration.TEST_STABILITY_CONFIG_KEY, externalTypes)
+        })
         val irClass = irModule.files.last().declarations.first() as IrClass
         val externalTypeMatchers = externalTypes.map { FqNameMatcher(it) }.toSet()
         val stabilityInferencer = StabilityInferencer(irModule.descriptor, externalTypeMatchers)
@@ -1615,7 +1651,13 @@
         externalTypes: Set<String> = emptySet(),
         packageName: String = "dependency"
     ) {
-        val irModule = buildModule(externalSrc, classDefSrc, dumpClasses, packageName)
+        val irModule = buildModule(
+            externalSrc,
+            classDefSrc,
+            dumpClasses,
+            packageName,
+            externalTypes = externalTypes
+        )
         val irClass = irModule.files.last().declarations.first() as IrClass
         val externalTypeMatchers = externalTypes.map { FqNameMatcher(it) }.toSet()
         val classStability =
@@ -1700,7 +1742,8 @@
 
                 fun TestFunction() = $expression
             """.trimIndent(),
-            dumpClasses
+            dumpClasses,
+            externalTypes = externalTypes
         )
         val irTestFn = irModule
             .files
@@ -1731,7 +1774,8 @@
         @Language("kotlin")
         localSrc: String,
         dumpClasses: Boolean = false,
-        packageName: String = "dependency"
+        packageName: String = "dependency",
+        externalTypes: Set<String>
     ): IrModuleFragment {
         val dependencyFileName = "Test_REPLACEME_${uniqueNumber++}"
         val dependencySrc = """
@@ -1771,7 +1815,10 @@
         """.trimIndent()
 
         val files = listOf(SourceFile("Test.kt", source))
-        return compileToIr(files, listOf(classesDirectory.root))
+        return compileToIr(files, listOf(classesDirectory.root), registerExtensions = {
+            it.put(ComposeConfiguration.TEST_STABILITY_CONFIG_KEY, externalTypes)
+            it.updateConfiguration()
+        })
     }
 
     private fun assertTransform(
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index bbc589f..c5c7fac 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -80,6 +80,10 @@
         CompilerConfigurationKey<List<String>>(
             "Path to stability configuration file"
         )
+    val TEST_STABILITY_CONFIG_KEY =
+        CompilerConfigurationKey<Set<String>>(
+            "Set of stable classes to be merged with configuration file, used for testing."
+        )
     val TRACE_MARKERS_ENABLED_KEY =
         CompilerConfigurationKey<Boolean>("Include composition trace markers in generated code")
 }
@@ -453,6 +457,10 @@
                 }
                 stableTypeMatchers.addAll(matchers)
             }
+            val testingMatchers = configuration.get(ComposeConfiguration.TEST_STABILITY_CONFIG_KEY)
+                ?.map { FqNameMatcher(it) }
+                ?: emptySet()
+            stableTypeMatchers.addAll(testingMatchers)
 
             return ComposeIrGenerationExtension(
                 liveLiteralsEnabled = liveLiteralsEnabled,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
index eee5edc..ba9179e 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
@@ -227,6 +227,11 @@
         ?.getValueArgument(0) as? IrConst<*>
         )?.value as? Int
 
+private data class SymbolForAnalysis(
+    val symbol: IrClassifierSymbol,
+    val typeParameters: List<IrTypeArgument?>
+)
+
 class StabilityInferencer(
     private val currentModule: ModuleDescriptor,
     externalStableTypeMatchers: Set<FqNameMatcher>
@@ -239,10 +244,13 @@
     private fun stabilityOf(
         declaration: IrClass,
         substitutions: Map<IrTypeParameterSymbol, IrTypeArgument>,
-        currentlyAnalyzing: Set<IrClassifierSymbol>
+        currentlyAnalyzing: Set<SymbolForAnalysis>
     ): Stability {
         val symbol = declaration.symbol
-        if (currentlyAnalyzing.contains(symbol)) return Stability.Unstable
+        val typeArguments = declaration.typeParameters.map { substitutions[it.symbol] }
+        val fullSymbol = SymbolForAnalysis(symbol, typeArguments)
+
+        if (currentlyAnalyzing.contains(fullSymbol)) return Stability.Unstable
         if (declaration.hasStableMarkedDescendant()) return Stability.Stable
         if (declaration.isEnumClass || declaration.isEnumEntry) return Stability.Stable
         if (declaration.defaultType.isPrimitiveType()) return Stability.Stable
@@ -252,7 +260,7 @@
             error("Builtins Stub: ${declaration.name}")
         }
 
-        val analyzing = currentlyAnalyzing + symbol
+        val analyzing = currentlyAnalyzing + fullSymbol
 
         if (canInferStability(declaration) || declaration.isExternalStableType()) {
             val fqName = declaration.fqNameWhenAvailable?.toString() ?: ""
@@ -352,7 +360,7 @@
     private fun stabilityOf(
         classifier: IrClassifierSymbol,
         substitutions: Map<IrTypeParameterSymbol, IrTypeArgument>,
-        currentlyAnalyzing: Set<IrClassifierSymbol>
+        currentlyAnalyzing: Set<SymbolForAnalysis>
     ): Stability {
         // if isEnum, return true
         // class hasStableAnnotation()
@@ -366,7 +374,7 @@
     private fun stabilityOf(
         argument: IrTypeArgument,
         substitutions: Map<IrTypeParameterSymbol, IrTypeArgument>,
-        currentlyAnalyzing: Set<IrClassifierSymbol>
+        currentlyAnalyzing: Set<SymbolForAnalysis>
     ): Stability {
         return when (argument) {
             is IrStarProjection -> Stability.Unstable
@@ -378,7 +386,7 @@
     private fun stabilityOf(
         type: IrType,
         substitutions: Map<IrTypeParameterSymbol, IrTypeArgument>,
-        currentlyAnalyzing: Set<IrClassifierSymbol>
+        currentlyAnalyzing: Set<SymbolForAnalysis>
     ): Stability {
         return when {
             type is IrErrorType -> Stability.Unstable
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/RowColumnModifierTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/RowColumnModifierTest.kt
index 2f40798..bec5e9b 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/RowColumnModifierTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/RowColumnModifierTest.kt
@@ -16,20 +16,33 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.background
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Measured
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInParent
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
+import kotlin.math.ceil
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -238,6 +251,194 @@
         }
     }
 
+    @OptIn(ExperimentalLayoutApi::class)
+    @Test
+    fun testRow_correctlyCalculatesIntrinsicCrossAxis() {
+        var totalFakeTextPlaced = 0
+
+        rule.setContent {
+            Row(
+                Modifier
+                    .width(200.dp)
+                    .background(Color.Green)
+                    .height(IntrinsicSize.Max)
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(24.dp)
+                        .background(Color.Blue)
+                )
+
+                Column(Modifier.wrapContentHeight()) {
+                    FakeText(modifier = Modifier.onPlaced {
+                        totalFakeTextPlaced++
+                    }, text = "Text")
+                    FlowRow(Modifier) {
+                        FakeText(
+                            modifier = Modifier.onPlaced {
+                                totalFakeTextPlaced++
+                        }, text = "Really long text 1")
+                        FakeText(modifier = Modifier.onPlaced {
+                            totalFakeTextPlaced++
+                        }, text = "Really long text 2")
+                    }
+                }
+
+                Box(
+                    modifier = Modifier
+                        .width(120.dp)
+                        .height(60.dp)
+                        .background(Color.Red)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalFakeTextPlaced).isEqualTo(3)
+        }
+    }
+
+    @OptIn(ExperimentalLayoutApi::class)
+    @Test
+    fun testColumn_correctlyCalculatesIntrinsicCrossAxis() {
+        var totalFakeTextPlaced = 0
+        val forRow = false
+        rule.setContent {
+            Column(
+                Modifier
+                    .height(176.dp)
+                    .background(Color.Green)
+                    .width(IntrinsicSize.Max)
+            ) {
+                Row(Modifier.wrapContentWidth()) {
+                    FakeText(modifier = Modifier.onPlaced {
+                        totalFakeTextPlaced++
+                    }, text = "Text", forRow)
+                    FlowColumn(Modifier) {
+                        FakeText(
+                            modifier = Modifier.onPlaced {
+                                totalFakeTextPlaced++
+                            }, text = "Really long text 1", forRow)
+                        FakeText(modifier = Modifier.onPlaced {
+                            totalFakeTextPlaced++
+                        }, text = "Really long text 2", forRow)
+                    }
+                }
+
+                Box(
+                    modifier = Modifier
+                        .width(60.dp)
+                        .height(120.dp)
+                        .background(Color.Red)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalFakeTextPlaced).isEqualTo(3)
+        }
+    }
+
+    /**
+     * @param forRow creates the bug setting for row. Otherwise, make it work for Column
+     * by laying out the text top to bottom.
+     */
+    @Composable
+    fun FakeText(modifier: Modifier = Modifier, text: String, forRow: Boolean = true) {
+        val characterSizeMainAxis = 8.dp
+        val textCrossAxisSize = 30.dp
+
+        val maxIntrinsicMainAxisSize = (characterSizeMainAxis * text.length)
+        val orientation = if (forRow) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
+        Layout(
+            content = {},
+            modifier = modifier,
+            measurePolicy = object : MeasurePolicy {
+                override fun MeasureScope.measure(
+                    measurables: List<Measurable>,
+                    constraints: Constraints
+                ): MeasureResult {
+                    val constraintsIndependent = OrientationIndependentConstraints(
+                        constraints,
+                        orientation
+                    )
+                    val maxMainAxis = constraintsIndependent.mainAxisMax
+                    val lengthNeeded = text.length * characterSizeMainAxis.roundToPx()
+                    val crossAxis = getCrossAxisNeeded(maxMainAxis)
+                    val mainAxis = lengthNeeded.coerceAtMost(maxMainAxis)
+
+                    var width: Int
+                    var height: Int
+                    if (forRow) {
+                        width = mainAxis
+                        height = crossAxis
+                    } else {
+                        width = crossAxis
+                        height = mainAxis
+                    }
+
+                    return layout(width, height) {
+                        measurables.forEach { measurable ->
+                            val placeable = measurable.measure(constraints)
+                            placeable.place(0, 0)
+                        }
+                    }
+                }
+
+                override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+                    measurables: List<IntrinsicMeasurable>,
+                    width: Int
+                ): Int {
+                    return if (forRow) {
+                        getCrossAxisNeeded(width)
+                    } else {
+                        maxIntrinsicMainAxisSize.roundToPx()
+                    }
+                }
+
+                override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+                    measurables: List<IntrinsicMeasurable>,
+                    height: Int
+                ): Int {
+                    return if (forRow) {
+                        maxIntrinsicMainAxisSize.roundToPx()
+                    } else {
+                        getCrossAxisNeeded(height)
+                    }
+                }
+
+                override fun IntrinsicMeasureScope.minIntrinsicHeight(
+                    measurables: List<IntrinsicMeasurable>,
+                    width: Int
+                ): Int {
+                    return if (forRow) {
+                        getCrossAxisNeeded(width)
+                    } else {
+                        characterSizeMainAxis.roundToPx()
+                    }
+                }
+
+                override fun IntrinsicMeasureScope.minIntrinsicWidth(
+                    measurables: List<IntrinsicMeasurable>,
+                    height: Int
+                ): Int {
+                    return if (forRow) {
+                        characterSizeMainAxis.roundToPx()
+                    } else {
+                        getCrossAxisNeeded(height)
+                    }
+                }
+
+                private fun IntrinsicMeasureScope.getCrossAxisNeeded(mainAxisSize: Int): Int {
+                    val lengthNeeded = text.length * characterSizeMainAxis.roundToPx()
+                    val noOfLines = if (mainAxisSize == Constraints.Infinity) 1 else
+                        ceil((lengthNeeded.toFloat() / mainAxisSize).toDouble()).toInt()
+                    return (textCrossAxisSize.roundToPx() * noOfLines)
+                }
+            }
+        )
+    }
+
     @Test
     fun testColumn_updatesOnAlignmentChange() {
         var positionInParentX = 0f
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index 953637a..a6c7d31 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -491,7 +491,7 @@
     ): Int {
         return intrinsicCrossAxisSize(
             measurables,
-            { w -> maxIntrinsicHeight(w) },
+            { w -> minIntrinsicHeight(w) },
             { h -> maxIntrinsicWidth(h) },
             availableHeight,
             mainAxisSpacing,
@@ -504,7 +504,7 @@
     ): Int {
         return intrinsicCrossAxisSize(
             measurables,
-            { h -> maxIntrinsicWidth(h) },
+            { h -> minIntrinsicWidth(h) },
             { w -> maxIntrinsicHeight(w) },
             availableWidth,
             mainAxisSpacing,
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index a59781b..962bffe 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -412,10 +412,12 @@
     method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
     method public float getLastVelocity();
     method public float getOffset();
-    method @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method public T getSettledValue();
     method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
     method public T getTargetValue();
     method public boolean isAnimationRunning();
+    method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
     method public float requireOffset();
     method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
@@ -425,7 +427,8 @@
     property public final boolean isAnimationRunning;
     property public final float lastVelocity;
     property public final float offset;
-    property @FloatRange(from=0.0, to=1.0) public final float progress;
+    property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
+    property public final T settledValue;
     property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
     property public final T targetValue;
     field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
@@ -452,13 +455,13 @@
   }
 
   public final class DragGestureDetectorKt {
-    method public static suspend Object? awaitDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitHorizontalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitLongPressOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitVerticalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitVerticalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
+    method public static suspend Object? awaitDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitHorizontalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitLongPressOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitVerticalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitVerticalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
     method public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static suspend Object? detectDragGesturesAfterLongPress(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -581,8 +584,8 @@
     method public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional boolean requireUnconsumed, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
     method @Deprecated public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional boolean requireUnconsumed, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
     method public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onTap, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method @Deprecated public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
+    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method @Deprecated public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
   }
 
   @androidx.compose.runtime.Stable public interface TargetedFlingBehavior extends androidx.compose.foundation.gestures.FlingBehavior {
@@ -1067,9 +1070,9 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static interface LazyLayoutIntervalContent.Interval {
     method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object>? getKey();
-    method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> getType();
+    method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> getType();
     property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object>? key;
-    property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> type;
+    property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> type;
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface LazyLayoutItemProvider {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 6d06791..6990af6 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -414,10 +414,12 @@
     method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
     method public float getLastVelocity();
     method public float getOffset();
-    method @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method public T getSettledValue();
     method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
     method public T getTargetValue();
     method public boolean isAnimationRunning();
+    method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
     method public float requireOffset();
     method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
@@ -427,7 +429,8 @@
     property public final boolean isAnimationRunning;
     property public final float lastVelocity;
     property public final float offset;
-    property @FloatRange(from=0.0, to=1.0) public final float progress;
+    property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
+    property public final T settledValue;
     property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
     property public final T targetValue;
     field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
@@ -454,13 +457,13 @@
   }
 
   public final class DragGestureDetectorKt {
-    method public static suspend Object? awaitDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitHorizontalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitLongPressOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitVerticalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method public static suspend Object? awaitVerticalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
+    method public static suspend Object? awaitDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitHorizontalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitLongPressOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitVerticalDragOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method public static suspend Object? awaitVerticalTouchSlopOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
     method public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static suspend Object? detectDragGesturesAfterLongPress(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStart, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -583,8 +586,8 @@
     method public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional boolean requireUnconsumed, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
     method @Deprecated public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional boolean requireUnconsumed, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
     method public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? onTap, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
-    method @Deprecated public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>);
+    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, optional androidx.compose.ui.input.pointer.PointerEventPass pass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
+    method @Deprecated public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange?>);
   }
 
   @androidx.compose.runtime.Stable public interface TargetedFlingBehavior extends androidx.compose.foundation.gestures.FlingBehavior {
@@ -1069,9 +1072,9 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static interface LazyLayoutIntervalContent.Interval {
     method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object>? getKey();
-    method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> getType();
+    method public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> getType();
     property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object>? key;
-    property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> type;
+    property public default kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> type;
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface LazyLayoutItemProvider {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
index 7a2cb4e..ddc8782 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.samples.AnchoredDraggableCatchAnimatingWidgetSample
 import androidx.compose.foundation.samples.AnchoredDraggableCustomAnchoredSample
 import androidx.compose.foundation.samples.AnchoredDraggableLayoutDependentAnchorsSample
+import androidx.compose.foundation.samples.AnchoredDraggableProgressSample
 import androidx.compose.foundation.samples.AnchoredDraggableWithOverscrollSample
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
@@ -44,5 +45,7 @@
         AnchoredDraggableCustomAnchoredSample()
         Spacer(Modifier.height(50.dp))
         AnchoredDraggableWithOverscrollSample()
+        Spacer(Modifier.height(50.dp))
+        AnchoredDraggableProgressSample()
     }
 }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/build.gradle b/compose/foundation/foundation/integration-tests/lazy-tests/build.gradle
new file mode 100644
index 0000000..b217966
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id("AndroidXComposePlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 21
+    }
+    namespace "androidx.compose.foundation.lazytests"
+}
+
+dependencies {
+    androidTestImplementation(project(":compose:foundation:foundation"))
+    androidTestImplementation(project(":compose:test-utils"))
+    androidTestImplementation(project(":internal-testutils-fonts"))
+    androidTestImplementation(project(":test:screenshot:screenshot"))
+    androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation("androidx.activity:activity-compose:1.3.1")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    androidTestImplementation("androidx.savedstate:savedstate:1.2.1")
+
+    androidTestImplementation(libs.kotlinTest)
+    androidTestImplementation(libs.kotlinCoroutinesTest)
+    androidTestImplementation(libs.testUiautomator)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testMonitor)
+    androidTestImplementation(libs.espressoCore)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.dexmakerMockito)
+    androidTestImplementation(libs.mockitoCore)
+    androidTestImplementation(libs.mockitoKotlin)
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml b/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml
new file mode 100644
index 0000000..cf9f22c
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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"
+        message="Uses Thread.sleep()"
+        errorLine1="            Thread.sleep(5)"
+        errorLine2="                   ~~~~~">
+        <location
+            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="            Thread.sleep(5)"
+        errorLine2="                   ~~~~~">
+        <location
+            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="            Thread.sleep(5)"
+        errorLine2="                   ~~~~~">
+        <location
+            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt"/>
+    </issue>
+
+</issues>
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt
new file mode 100644
index 0000000..b889918
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 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
+
+import androidx.compose.runtime.MonotonicFrameClock
+import java.util.concurrent.atomic.AtomicLong
+
+class AutoTestFrameClock : MonotonicFrameClock {
+    private val time = AtomicLong(0)
+
+    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+        return onFrame(time.getAndAdd(16_000_000))
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
new file mode 100644
index 0000000..571c316
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 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
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.grid.scrollBy
+import androidx.compose.runtime.Stable
+import androidx.compose.testutils.assertIsEqualTo
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import org.junit.Rule
+
+open class BaseLazyLayoutTestWithOrientation(private val orientation: Orientation) {
+    @get:Rule
+    val rule = createComposeRule()
+
+    val vertical: Boolean
+        get() = orientation == Orientation.Vertical
+
+    @Stable
+    fun Modifier.crossAxisSize(size: Dp) =
+        if (vertical) {
+            this.width(size)
+        } else {
+            this.height(size)
+        }
+
+    @Stable
+    fun Modifier.mainAxisSize(size: Dp) =
+        if (vertical) {
+            this.height(size)
+        } else {
+            this.width(size)
+        }
+
+    @Stable
+    fun Modifier.axisSize(crossAxis: Dp, mainAxis: Dp) =
+        if (vertical) {
+            this.size(crossAxis, mainAxis)
+        } else {
+            this.size(mainAxis, crossAxis)
+        }
+
+    fun SemanticsNodeInteraction.scrollMainAxisBy(distance: Dp) {
+        if (vertical) {
+            this.scrollBy(y = distance, density = rule.density)
+        } else {
+            this.scrollBy(x = distance, density = rule.density)
+        }
+    }
+
+    fun SemanticsNodeInteraction.assertMainAxisSizeIsEqualTo(expectedSize: Dp) =
+        if (vertical) {
+            assertHeightIsEqualTo(expectedSize)
+        } else {
+            assertWidthIsEqualTo(expectedSize)
+        }
+
+    fun SemanticsNodeInteraction.assertCrossAxisSizeIsEqualTo(expectedSize: Dp) =
+        if (vertical) {
+            assertWidthIsEqualTo(expectedSize)
+        } else {
+            assertHeightIsEqualTo(expectedSize)
+        }
+
+    fun SemanticsNodeInteraction.assertStartPositionIsAlmost(expected: Dp) {
+        val position = if (vertical) {
+            getUnclippedBoundsInRoot().top
+        } else {
+            getUnclippedBoundsInRoot().left
+        }
+        position.assertIsEqualTo(expected, tolerance = 1.dp)
+    }
+
+    fun SemanticsNodeInteraction.assertMainAxisStartPositionInRootIsEqualTo(expectedStart: Dp) =
+        if (vertical) {
+            assertTopPositionInRootIsEqualTo(expectedStart)
+        } else {
+            assertLeftPositionInRootIsEqualTo(expectedStart)
+        }
+
+    fun SemanticsNodeInteraction.assertStartPositionInRootIsEqualTo(expectedStart: Dp) =
+        if (vertical) {
+            assertTopPositionInRootIsEqualTo(expectedStart)
+        } else {
+            assertLeftPositionInRootIsEqualTo(expectedStart)
+        }
+
+    fun SemanticsNodeInteraction.assertCrossAxisStartPositionInRootIsEqualTo(expectedStart: Dp) =
+        if (vertical) {
+            assertLeftPositionInRootIsEqualTo(expectedStart)
+        } else {
+            assertTopPositionInRootIsEqualTo(expectedStart)
+        }
+
+    fun SemanticsNodeInteraction.assertAxisBounds(
+        offset: DpOffset,
+        size: DpSize
+    ) =
+        assertMainAxisStartPositionInRootIsEqualTo(offset.y)
+            .assertCrossAxisStartPositionInRootIsEqualTo(offset.x)
+            .assertMainAxisSizeIsEqualTo(size.height)
+            .assertCrossAxisSizeIsEqualTo(size.width)
+
+    fun PaddingValues(
+        mainAxis: Dp = 0.dp,
+        crossAxis: Dp = 0.dp
+    ) = PaddingValues(
+        beforeContent = mainAxis,
+        afterContent = mainAxis,
+        beforeContentCrossAxis = crossAxis,
+        afterContentCrossAxis = crossAxis
+    )
+
+    fun PaddingValues(
+        beforeContent: Dp = 0.dp,
+        afterContent: Dp = 0.dp,
+        beforeContentCrossAxis: Dp = 0.dp,
+        afterContentCrossAxis: Dp = 0.dp,
+    ) = if (vertical) {
+        androidx.compose.foundation.layout.PaddingValues(
+            start = beforeContentCrossAxis,
+            top = beforeContent,
+            end = afterContentCrossAxis,
+            bottom = afterContent
+        )
+    } else {
+        androidx.compose.foundation.layout.PaddingValues(
+            start = beforeContent,
+            top = beforeContentCrossAxis,
+            end = afterContent,
+            bottom = afterContentCrossAxis
+        )
+    }
+
+    internal fun Modifier.debugBorder(color: Color = Color.Black) = border(1.dp, color)
+
+    companion object {
+        internal const val FrameDuration = 16L
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
new file mode 100644
index 0000000..18eedb5
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
@@ -0,0 +1,164 @@
+/*
+ * 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
+
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.util.fastForEach
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.CoordinatesProvider
+import androidx.test.espresso.action.GeneralLocation
+import androidx.test.espresso.action.GeneralSwipeAction
+import androidx.test.espresso.action.Press
+import androidx.test.espresso.action.Swipe
+import kotlinx.coroutines.coroutineScope
+import org.hamcrest.CoreMatchers
+
+// Very low tolerance on the difference
+internal val VelocityTrackerCalculationThreshold = 1
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal suspend fun savePointerInputEvents(
+    tracker: VelocityTracker,
+    pointerInputScope: PointerInputScope
+) {
+    if (VelocityTrackerAddPointsFix) {
+        savePointerInputEventsWithFix(tracker, pointerInputScope)
+    } else {
+        savePointerInputEventsLegacy(tracker, pointerInputScope)
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal suspend fun savePointerInputEventsWithFix(
+    tracker: VelocityTracker,
+    pointerInputScope: PointerInputScope
+) {
+    with(pointerInputScope) {
+        coroutineScope {
+            awaitPointerEventScope {
+                while (true) {
+                    var event: PointerInputChange? = awaitFirstDown()
+                    while (event != null && !event.changedToUpIgnoreConsumed()) {
+                        val currentEvent = awaitPointerEvent().changes
+                            .firstOrNull()
+
+                        if (currentEvent != null && !currentEvent.changedToUpIgnoreConsumed()) {
+                            currentEvent.historical.fastForEach {
+                                tracker.addPosition(it.uptimeMillis, it.position)
+                            }
+                            tracker.addPosition(
+                                currentEvent.uptimeMillis,
+                                currentEvent.position
+                            )
+                        }
+
+                        event = currentEvent
+                    }
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal suspend fun savePointerInputEventsLegacy(
+    tracker: VelocityTracker,
+    pointerInputScope: PointerInputScope
+) {
+    with(pointerInputScope) {
+        coroutineScope {
+            awaitPointerEventScope {
+                while (true) {
+                    var event = awaitFirstDown()
+                    tracker.addPosition(event.uptimeMillis, event.position)
+                    while (!event.changedToUpIgnoreConsumed()) {
+                        val currentEvent = awaitPointerEvent().changes
+                            .firstOrNull()
+
+                        if (currentEvent != null) {
+                            currentEvent.historical.fastForEach {
+                                tracker.addPosition(it.uptimeMillis, it.position)
+                            }
+                            tracker.addPosition(
+                                currentEvent.uptimeMillis,
+                                currentEvent.position
+                            )
+                            event = currentEvent
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+internal fun composeViewSwipeUp() {
+    Espresso.onView(CoreMatchers.allOf(CoreMatchers.instanceOf(AbstractComposeView::class.java)))
+        .perform(
+            espressoSwipe(
+                GeneralLocation.CENTER,
+                GeneralLocation.TOP_CENTER
+            )
+        )
+}
+
+internal fun composeViewSwipeDown() {
+    Espresso.onView(CoreMatchers.allOf(CoreMatchers.instanceOf(AbstractComposeView::class.java)))
+        .perform(
+            espressoSwipe(
+                GeneralLocation.CENTER,
+                GeneralLocation.BOTTOM_CENTER
+            )
+        )
+}
+
+internal fun composeViewSwipeLeft() {
+    Espresso.onView(CoreMatchers.allOf(CoreMatchers.instanceOf(AbstractComposeView::class.java)))
+        .perform(
+            espressoSwipe(
+                GeneralLocation.CENTER,
+                GeneralLocation.CENTER_LEFT
+            )
+        )
+}
+
+internal fun composeViewSwipeRight() {
+    Espresso.onView(CoreMatchers.allOf(CoreMatchers.instanceOf(AbstractComposeView::class.java)))
+        .perform(
+            espressoSwipe(
+                GeneralLocation.CENTER,
+                GeneralLocation.CENTER_RIGHT
+            )
+        )
+}
+
+private fun espressoSwipe(
+    start: CoordinatesProvider,
+    end: CoordinatesProvider
+): GeneralSwipeAction {
+    return GeneralSwipeAction(
+        Swipe.FAST, start, end,
+        Press.FINGER
+    )
+}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
new file mode 100644
index 0000000..0bf7bf5
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2021 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.grid
+
+import androidx.compose.animation.core.snap
+import androidx.compose.foundation.AutoTestFrameClock
+import androidx.compose.foundation.BaseLazyLayoutTestWithOrientation
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+
+open class BaseLazyGridTestWithOrientation(
+    orientation: Orientation
+) : BaseLazyLayoutTestWithOrientation(orientation) {
+
+    fun LazyGridState.scrollBy(offset: Dp) {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            animateScrollBy(with(rule.density) { offset.roundToPx().toFloat() }, snap())
+        }
+    }
+
+    fun LazyGridState.scrollTo(index: Int) {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            scrollToItem(index)
+        }
+    }
+
+    fun SemanticsNodeInteraction.scrollBy(offset: Dp) = scrollMainAxisBy(offset)
+
+    @Composable
+    fun LazyGrid(
+        cells: Int,
+        modifier: Modifier = Modifier,
+        state: LazyGridState = rememberLazyGridState(),
+        contentPadding: PaddingValues = PaddingValues(0.dp),
+        reverseLayout: Boolean = false,
+        reverseArrangement: Boolean = false,
+        flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+        userScrollEnabled: Boolean = true,
+        crossAxisSpacedBy: Dp = 0.dp,
+        mainAxisSpacedBy: Dp = 0.dp,
+        content: LazyGridScope.() -> Unit
+    ) = LazyGrid(
+        GridCells.Fixed(cells),
+        modifier,
+        state,
+        contentPadding,
+        reverseLayout,
+        reverseArrangement,
+        flingBehavior,
+        userScrollEnabled,
+        crossAxisSpacedBy,
+        mainAxisSpacedBy,
+        content
+    )
+
+    @Composable
+    fun LazyGrid(
+        cells: GridCells,
+        modifier: Modifier = Modifier,
+        state: LazyGridState = rememberLazyGridState(),
+        contentPadding: PaddingValues = PaddingValues(0.dp),
+        reverseLayout: Boolean = false,
+        reverseArrangement: Boolean = false,
+        flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+        userScrollEnabled: Boolean = true,
+        crossAxisSpacedBy: Dp = 0.dp,
+        mainAxisSpacedBy: Dp = 0.dp,
+        content: LazyGridScope.() -> Unit
+    ) {
+        if (vertical) {
+            val verticalArrangement = when {
+                mainAxisSpacedBy != 0.dp -> Arrangement.spacedBy(mainAxisSpacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.Bottom
+                else -> Arrangement.Top
+            }
+            val horizontalArrangement = when {
+                crossAxisSpacedBy != 0.dp -> Arrangement.spacedBy(crossAxisSpacedBy)
+                else -> Arrangement.Start
+            }
+            LazyVerticalGrid(
+                columns = cells,
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                verticalArrangement = verticalArrangement,
+                horizontalArrangement = horizontalArrangement,
+                content = content
+            )
+        } else {
+            val horizontalArrangement = when {
+                mainAxisSpacedBy != 0.dp -> Arrangement.spacedBy(mainAxisSpacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.End
+                else -> Arrangement.Start
+            }
+            val verticalArrangement = when {
+                crossAxisSpacedBy != 0.dp -> Arrangement.spacedBy(crossAxisSpacedBy)
+                else -> Arrangement.Top
+            }
+            LazyHorizontalGrid(
+                rows = cells,
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                horizontalArrangement = horizontalArrangement,
+                verticalArrangement = verticalArrangement,
+                content = content
+            )
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyArrangementsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyArrangementsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyArrangementsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyArrangementsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyCustomKeysTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyCustomKeysTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyCustomKeysTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyCustomKeysTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPinnableContainerTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPinnableContainerTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPinnableContainerTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPinnableContainerTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPrefetcherTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSlotsReuseTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSlotsReuseTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSlotsReuseTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSlotsReuseTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
index b26ab18..b94d36c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.grid
 
 import android.os.Build
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsIndexedTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsIndexedTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsIndexedTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsIndexedTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyNestedScrollingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyNestedScrollingTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyNestedScrollingTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyNestedScrollingTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollAccessibilityTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollAccessibilityTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollAccessibilityTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollAccessibilityTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazySemanticsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazySemanticsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazySemanticsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazySemanticsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
new file mode 100644
index 0000000..a1110337
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2021 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("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
+package androidx.compose.foundation.lazy.list
+
+import androidx.compose.animation.core.snap
+import androidx.compose.foundation.AutoTestFrameClock
+import androidx.compose.foundation.BaseLazyLayoutTestWithOrientation
+import androidx.compose.foundation.composeViewSwipeDown
+import androidx.compose.foundation.composeViewSwipeLeft
+import androidx.compose.foundation.composeViewSwipeRight
+import androidx.compose.foundation.composeViewSwipeUp
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyItemScope
+import androidx.compose.foundation.lazy.LazyList
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+
+open class BaseLazyListTestWithOrientation(
+    private val orientation: Orientation
+) : BaseLazyLayoutTestWithOrientation(orientation) {
+
+    fun Modifier.fillMaxCrossAxis() =
+        if (vertical) {
+            this.fillMaxWidth()
+        } else {
+            this.fillMaxHeight()
+        }
+
+    fun LazyItemScope.fillParentMaxMainAxis() =
+        if (vertical) {
+            Modifier.fillParentMaxHeight()
+        } else {
+            Modifier.fillParentMaxWidth()
+        }
+
+    fun LazyItemScope.fillParentMaxCrossAxis() =
+        if (vertical) {
+            Modifier.fillParentMaxWidth()
+        } else {
+            Modifier.fillParentMaxHeight()
+        }
+
+    fun LazyListState.scrollBy(offset: Dp) {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            animateScrollBy(with(rule.density) { offset.roundToPx().toFloat() }, snap())
+        }
+    }
+
+    fun LazyListState.scrollTo(index: Int) {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            scrollToItem(index)
+        }
+    }
+
+    fun SemanticsNodeInteraction.scrollBy(offset: Dp) = scrollBy(
+        x = if (vertical) 0.dp else offset,
+        y = if (!vertical) 0.dp else offset,
+        density = rule.density
+    )
+
+    fun composeViewSwipeForward() {
+        if (orientation == Orientation.Vertical) {
+            composeViewSwipeUp()
+        } else {
+            composeViewSwipeLeft()
+        }
+    }
+
+    fun composeViewSwipeBackward() {
+        if (orientation == Orientation.Vertical) {
+            composeViewSwipeDown()
+        } else {
+            composeViewSwipeRight()
+        }
+    }
+
+    fun Velocity.toFloat(): Float {
+        return if (orientation == Orientation.Vertical) y else x
+    }
+
+    @Composable
+    fun LazyColumnOrRow(
+        modifier: Modifier = Modifier,
+        state: LazyListState = rememberLazyListState(),
+        contentPadding: PaddingValues = PaddingValues(0.dp),
+        reverseLayout: Boolean = false,
+        reverseArrangement: Boolean = false,
+        flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+        userScrollEnabled: Boolean = true,
+        spacedBy: Dp = 0.dp,
+        content: LazyListScope.() -> Unit
+    ) {
+        if (vertical) {
+            val verticalArrangement = when {
+                spacedBy != 0.dp -> Arrangement.spacedBy(spacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.Bottom
+                else -> Arrangement.Top
+            }
+            LazyColumn(
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                verticalArrangement = verticalArrangement,
+                content = content
+            )
+        } else {
+            val horizontalArrangement = when {
+                spacedBy != 0.dp -> Arrangement.spacedBy(spacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.End
+                else -> Arrangement.Start
+            }
+            LazyRow(
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                horizontalArrangement = horizontalArrangement,
+                content = content
+            )
+        }
+    }
+
+    @Composable
+    fun LazyColumnOrRow(
+        modifier: Modifier = Modifier,
+        state: LazyListState = rememberLazyListState(),
+        contentPadding: PaddingValues = PaddingValues(0.dp),
+        reverseLayout: Boolean = false,
+        reverseArrangement: Boolean = false,
+        flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+        userScrollEnabled: Boolean = true,
+        spacedBy: Dp = 0.dp,
+        beyondBoundsItemCount: Int,
+        content: LazyListScope.() -> Unit
+    ) {
+        if (vertical) {
+            val verticalArrangement = when {
+                spacedBy != 0.dp -> Arrangement.spacedBy(spacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.Bottom
+                else -> Arrangement.Top
+            }
+            LazyColumn(
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                verticalArrangement = verticalArrangement,
+                beyondBoundsItemCount = beyondBoundsItemCount,
+                content = content
+            )
+        } else {
+            val horizontalArrangement = when {
+                spacedBy != 0.dp -> Arrangement.spacedBy(spacedBy)
+                reverseLayout xor reverseArrangement -> Arrangement.End
+                else -> Arrangement.Start
+            }
+            LazyRow(
+                modifier = modifier,
+                state = state,
+                contentPadding = contentPadding,
+                reverseLayout = reverseLayout,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+                horizontalArrangement = horizontalArrangement,
+                beyondBoundsItemCount = beyondBoundsItemCount,
+                content = content
+            )
+        }
+    }
+}
+
+@Composable
+private fun LazyColumn(
+    modifier: Modifier = Modifier,
+    state: LazyListState = rememberLazyListState(),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
+    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    userScrollEnabled: Boolean = true,
+    beyondBoundsItemCount: Int,
+    content: LazyListScope.() -> Unit
+) {
+    LazyList(
+        modifier = modifier,
+        state = state,
+        contentPadding = contentPadding,
+        flingBehavior = flingBehavior,
+        horizontalAlignment = horizontalAlignment,
+        verticalArrangement = verticalArrangement,
+        isVertical = true,
+        reverseLayout = reverseLayout,
+        userScrollEnabled = userScrollEnabled,
+        beyondBoundsItemCount = beyondBoundsItemCount,
+        content = content
+    )
+}
+
+@Composable
+private fun LazyRow(
+    modifier: Modifier = Modifier,
+    state: LazyListState = rememberLazyListState(),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    horizontalArrangement: Arrangement.Horizontal =
+        if (!reverseLayout) Arrangement.Start else Arrangement.End,
+    verticalAlignment: Alignment.Vertical = Alignment.Top,
+    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    userScrollEnabled: Boolean = true,
+    beyondBoundsItemCount: Int,
+    content: LazyListScope.() -> Unit
+) {
+    LazyList(
+        modifier = modifier,
+        state = state,
+        contentPadding = contentPadding,
+        verticalAlignment = verticalAlignment,
+        horizontalArrangement = horizontalArrangement,
+        isVertical = false,
+        flingBehavior = flingBehavior,
+        reverseLayout = reverseLayout,
+        userScrollEnabled = userScrollEnabled,
+        beyondBoundsItemCount = beyondBoundsItemCount,
+        content = content
+    )
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyArrangementsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyArrangementsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyArrangementsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyArrangementsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt
index 3dadcfb..96d5b43 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import android.os.Build
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyCustomKeysTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyCustomKeysTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyCustomKeysTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyCustomKeysTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsItemCountTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsItemCountTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsItemCountTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsItemCountTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
index 587e98b..b99984c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import androidx.compose.foundation.ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
index d323292..9350a50 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import android.os.Build
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetchStrategyTest.kt
index 94f0250..f64b86a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/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
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import androidx.compose.foundation.ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPrefetcherTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListSlotsReuseTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListSlotsReuseTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListSlotsReuseTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListSlotsReuseTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index da3f43f..0fda009 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import android.os.Build
@@ -49,7 +51,6 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.savePointerInputEvents
 import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.matchers.isZero
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -2214,7 +2215,7 @@
             .scrollMainAxisBy(250.dp) // 10 items, half a screen
 
         rule.runOnIdle {
-            assertThat(composedMoreThanOnce).isZero()
+            assertThat(composedMoreThanOnce).isEqualTo(0)
 
             assertTrue(
                 "Items are expected to be composed only once.",
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
index 0f07e16..d239d15 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import androidx.compose.foundation.gestures.FlingBehavior
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsReverseLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsReverseLayoutTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsReverseLayoutTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsReverseLayoutTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyNestedScrollingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyNestedScrollingTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyNestedScrollingTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyNestedScrollingTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyRowTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyRowTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyRowTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyRowTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollAccessibilityTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollAccessibilityTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollAccessibilityTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollAccessibilityTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazySemanticsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazySemanticsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazySemanticsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazySemanticsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCustomKeysTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCustomKeysTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCustomKeysTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCustomKeysTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
similarity index 97%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
index 3c15d97..fa7fe4f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.staggeredgrid
 
 import com.google.common.truth.Truth.assertThat
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPinnableContainerTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPinnableContainerTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPinnableContainerTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPinnableContainerTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/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
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridReverseLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridReverseLayoutTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridReverseLayoutTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridReverseLayoutTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
similarity index 100%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
rename to compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
index a376481..fc376b4d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.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
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
index 69c8223..81a89a8 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
@@ -30,25 +30,39 @@
 import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.overscroll
+import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.Center
+import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.End
+import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.HalfEnd
+import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.HalfStart
+import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.Start
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
+import kotlin.math.max
 import kotlin.math.roundToInt
 
 private enum class AnchoredDraggableSampleValue {
-    Start, Center, End
+    Start, HalfStart, Center, HalfEnd, End
 }
 
 @Composable
@@ -69,7 +83,7 @@
         )
     ) {
         AnchoredDraggableState(
-            initialValue = AnchoredDraggableSampleValue.Center,
+            initialValue = Center,
             positionalThreshold,
             velocityThreshold,
             snapAnimationSpec,
@@ -83,9 +97,9 @@
     SideEffect {
         state.updateAnchors(
             DraggableAnchors {
-                AnchoredDraggableSampleValue.Start at 0f
-                AnchoredDraggableSampleValue.Center at containerWidthPx / 2f
-                AnchoredDraggableSampleValue.End at containerWidthPx
+                Start at 0f
+                Center at containerWidthPx / 2f
+                End at containerWidthPx
             }
         )
     }
@@ -124,14 +138,14 @@
         )
     ) {
         AnchoredDraggableState(
-            initialValue = AnchoredDraggableSampleValue.Center,
+            initialValue = Center,
             positionalThreshold,
             velocityThreshold,
             snapAnimationSpec,
             decayAnimationSpec
         )
     }
-    val draggableSize = 100.dp
+    val draggableSize = 60.dp
     val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
     Box(
         Modifier
@@ -142,16 +156,19 @@
                 val dragEndPoint = layoutSize.width - draggableSizePx
                 state.updateAnchors(
                     DraggableAnchors {
-                        AnchoredDraggableSampleValue.Start at 0f
-                        AnchoredDraggableSampleValue.Center at dragEndPoint / 2f
-                        AnchoredDraggableSampleValue.End at dragEndPoint
+                        Start at 0f
+                        HalfStart at dragEndPoint * .25f
+                        Center at dragEndPoint * .5f
+                        HalfEnd at dragEndPoint * .75f
+                        End at dragEndPoint
                     }
                 )
             }
+            .visualizeDraggableAnchors(state, Orientation.Horizontal)
     ) {
         Box(
             Modifier
-                .size(100.dp)
+                .size(draggableSize)
                 .offset {
                     IntOffset(
                         x = state
@@ -205,7 +222,7 @@
     // or drag the settling box.
     val snapAnimationSpec = tween<Float>(durationMillis = 3000)
     val state = AnchoredDraggableState(
-        initialValue = AnchoredDraggableSampleValue.Start,
+        initialValue = Start,
         positionalThreshold = { distance: Float -> distance * 0.5f },
         velocityThreshold = { with(density) { 125.dp.toPx() } },
         snapAnimationSpec = snapAnimationSpec,
@@ -221,8 +238,8 @@
                 val dragEndPoint = layoutSize.width - draggableSizePx
                 state.updateAnchors(
                     DraggableAnchors {
-                        AnchoredDraggableSampleValue.Start at 0f
-                        AnchoredDraggableSampleValue.End at dragEndPoint
+                        Start at 0f
+                        End at dragEndPoint
                     }
                 )
             }
@@ -266,7 +283,7 @@
         )
     ) {
         AnchoredDraggableState(
-            initialValue = AnchoredDraggableSampleValue.Center,
+            initialValue = Center,
             positionalThreshold,
             velocityThreshold,
             animationSpec,
@@ -281,9 +298,9 @@
                 val dragEndPoint = layoutSize.width - draggableSizePx
                 state.updateAnchors(
                     DraggableAnchors {
-                        AnchoredDraggableSampleValue.Start at 0f
-                        AnchoredDraggableSampleValue.Center at dragEndPoint / 2f
-                        AnchoredDraggableSampleValue.End at dragEndPoint
+                        Start at 0f
+                        Center at dragEndPoint / 2f
+                        End at dragEndPoint
                     }
                 )
             }
@@ -309,3 +326,115 @@
         )
     }
 }
+
+@Composable
+fun AnchoredDraggableProgressSample() {
+    val density = LocalDensity.current
+    val snapAnimationSpec = tween<Float>()
+    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
+    val positionalThreshold = { distance: Float -> distance * 0.5f }
+    val velocityThreshold = { with(density) { 125.dp.toPx() } }
+    val state = rememberSaveable(
+        density,
+        saver = AnchoredDraggableState.Saver(
+            snapAnimationSpec,
+            decayAnimationSpec,
+            positionalThreshold,
+            velocityThreshold
+        )
+    ) {
+        AnchoredDraggableState(
+            initialValue = Center,
+            positionalThreshold,
+            velocityThreshold,
+            snapAnimationSpec,
+            decayAnimationSpec
+        )
+    }
+    val draggableSize = 60.dp
+    val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
+    Column(
+        Modifier
+            .fillMaxWidth()
+            // Our anchors depend on this box's size, so we obtain the size from onSizeChanged and
+            // use updateAnchors to let the state know about the new anchors
+            .onSizeChanged { layoutSize ->
+                val dragEndPoint = layoutSize.width - draggableSizePx
+                state.updateAnchors(
+                    DraggableAnchors {
+                        Start at 0f
+                        Center at dragEndPoint * .5f
+                        End at dragEndPoint
+                    }
+                )
+            }
+    ) {
+        // Read progress in a snapshot-backed context to receive updates. This could be e.g. a
+        //  derived state, snapshotFlow or other snapshot-aware context like the graphicsLayer
+        //  block.
+        val centerToStartProgress by derivedStateOf { state.progress(from = Center, to = Start) }
+        val centerToEndProgress by derivedStateOf { state.progress(from = Center, to = End) }
+        Box {
+            Box(
+                Modifier
+                    .fillMaxWidth()
+                    .height(draggableSize)
+                    .graphicsLayer { alpha = max(centerToStartProgress, centerToEndProgress) }
+                    .background(Color.Black)
+            )
+            Box(
+                Modifier
+                    .size(draggableSize)
+                    .offset {
+                        IntOffset(
+                            x = state
+                                .requireOffset()
+                                .roundToInt(), y = 0
+                        )
+                    }
+                    .anchoredDraggable(state, Orientation.Horizontal)
+                    .background(Color.Red)
+            )
+        }
+    }
+}
+
+/**
+ * A [Modifier] that visualizes the anchors attached to an [AnchoredDraggableState] as lines along
+ * the cross axis of the layout (start to end for [Orientation.Vertical], top to end for
+ * [Orientation.Horizontal]).
+ * This is useful to debug components with a complex set of anchors, or for AnchoredDraggable
+ * development.
+ *
+ * @param state The state whose anchors to visualize
+ * @param orientation The orientation of the [anchoredDraggable]
+ * @param lineColor The color of the visualization lines
+ * @param lineStrokeWidth The stroke width of the visualization lines
+ * @param linePathEffect The path effect used to draw the visualization lines
+ */
+private fun Modifier.visualizeDraggableAnchors(
+    state: AnchoredDraggableState<*>,
+    orientation: Orientation,
+    lineColor: Color = Color.Black,
+    lineStrokeWidth: Float = 10f,
+    linePathEffect: PathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 30f))
+) = drawWithContent {
+    drawContent()
+    state.anchors.forEach { _, position ->
+        val startOffset = Offset(
+            x = if (orientation == Orientation.Horizontal) position else 0f,
+            y = if (orientation == Orientation.Vertical) position else 0f
+        )
+        val endOffset = Offset(
+            x = if (orientation == Orientation.Horizontal) startOffset.x else size.height,
+            y = if (orientation == Orientation.Vertical) startOffset.y else size.width
+        )
+        drawLine(
+            color = lineColor,
+            start = startOffset,
+            end = endOffset,
+            strokeWidth = lineStrokeWidth,
+            pathEffect = linePathEffect
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
index 571c316..0425e5a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
@@ -20,10 +20,10 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.grid.scrollBy
 import androidx.compose.runtime.Stable
 import androidx.compose.testutils.assertIsEqualTo
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -32,6 +32,9 @@
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.DpSize
@@ -69,6 +72,22 @@
             this.size(mainAxis, crossAxis)
         }
 
+    fun SemanticsNodeInteraction.scrollBy(x: Dp = 0.dp, y: Dp = 0.dp, density: Density) =
+        performTouchInput {
+            with(density) {
+                val touchSlop = TestTouchSlop.toInt()
+                val xPx = x.roundToPx()
+                val yPx = y.roundToPx()
+                val offsetX = if (xPx > 0) xPx + touchSlop else if (xPx < 0) xPx - touchSlop else 0
+                val offsetY = if (yPx > 0) yPx + touchSlop else if (yPx < 0) yPx - touchSlop else 0
+                swipeWithVelocity(
+                    start = center,
+                    end = Offset(center.x - offsetX, center.y - offsetY),
+                    endVelocity = 0f
+                )
+            }
+        }
+
     fun SemanticsNodeInteraction.scrollMainAxisBy(distance: Dp) {
         if (vertical) {
             this.scrollBy(y = distance, density = rule.density)
diff --git a/appcompat/appcompat/src/main/java/DeleteMe.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/TouchUtils.kt
similarity index 81%
rename from appcompat/appcompat/src/main/java/DeleteMe.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/TouchUtils.kt
index 38f8b7a..87d8319 100644
--- a/appcompat/appcompat/src/main/java/DeleteMe.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/TouchUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,4 +14,6 @@
  * limitations under the License.
  */
 
-// This file exists to trick AGP/lint to work around b/234865137
+package androidx.compose.foundation
+
+internal const val TestTouchSlop = 18f
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
index 72464a7..a5c32eb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
@@ -92,8 +92,8 @@
         )
         val anchors = DraggableAnchors {
             A at 0f
-            B at 250f
-            C at 500f
+            B at AnchoredDraggableBoxSize.value / 2f
+            C at AnchoredDraggableBoxSize.value
         }
         state.updateAnchors(anchors)
 
@@ -164,8 +164,8 @@
         )
         val anchors = DraggableAnchors {
             A at 0f
-            B at 250f
-            C at 500f
+            B at AnchoredDraggableBoxSize.value / 2f
+            C at AnchoredDraggableBoxSize.value
         }
         state.updateAnchors(anchors)
 
@@ -870,7 +870,7 @@
         val velocityThreshold = 100.dp
         val state = AnchoredDraggableState(
             initialValue = A,
-            velocityThreshold = { with(rule.density) { velocityThreshold.toPx() } },
+            velocityThreshold = { 0f },
             positionalThreshold = { Float.POSITIVE_INFINITY },
             snapAnimationSpec = tween(),
             decayAnimationSpec = DefaultDecayAnimationSpec
@@ -909,13 +909,13 @@
             .performTouchInput {
                 swipeWithVelocity(
                     start = Offset(left, 0f),
-                    end = Offset(right / 2, 0f),
+                    end = Offset(right / 4, 0f),
                     endVelocity = with(rule.density) { velocityThreshold.toPx() } * 0.9f
                 )
             }
 
         rule.waitForIdle()
-        assertThat(state.currentValue).isEqualTo(A)
+        assertThat(state.settledValue).isEqualTo(A)
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
index 58a4c3f..84885e5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
@@ -53,7 +53,6 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.withFrameNanos
-import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onSizeChanged
@@ -286,66 +285,83 @@
     }
 
     @Test
-    fun anchoredDraggable_progress_matchesSwipePosition() {
+    fun anchoredDraggable_progress() {
+        rule.mainClock.autoAdvance = false
+        val animationDuration = 320
+        val frameLengthMillis = 16
+        val amountOfFramesForAnimation = animationDuration / frameLengthMillis
         val state = AnchoredDraggableState(
             initialValue = A,
-            positionalThreshold = defaultPositionalThreshold,
+            snapAnimationSpec = tween(animationDuration, easing = LinearEasing),
+            positionalThreshold = { distance -> distance * 0.5f },
             velocityThreshold = defaultVelocityThreshold,
-            snapAnimationSpec = defaultAnimationSpec,
             decayAnimationSpec = defaultDecayAnimationSpec
         )
+        lateinit var scope: CoroutineScope
         rule.setContent {
-            WithTouchSlop(touchSlop = 0f) {
-                Box(Modifier.fillMaxSize()) {
-                    Box(
-                        Modifier
-                            .requiredSize(AnchoredDraggableBoxSize)
-                            .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Vertical
+            scope = rememberCoroutineScope()
+            Box(Modifier.fillMaxSize()) {
+                Box(
+                    Modifier
+                        .requiredSize(AnchoredDraggableBoxSize)
+                        .testTag(AnchoredDraggableTestTag)
+                        .anchoredDraggable(
+                            state = state,
+                            orientation = Orientation.Vertical
+                        )
+                        .onSizeChanged { layoutSize ->
+                            state.updateAnchors(
+                                DraggableAnchors {
+                                    A at 0f
+                                    B at layoutSize.width / 2f
+                                    C at layoutSize.width.toFloat()
+                                }
                             )
-                            .onSizeChanged { layoutSize ->
-                                state.updateAnchors(
-                                    DraggableAnchors {
-                                        A at 0f
-                                        B at layoutSize.width / 2f
-                                        C at layoutSize.width.toFloat()
-                                    }
-                                )
-                            }
-                            .offset {
-                                IntOffset(
-                                    state
-                                        .requireOffset()
-                                        .roundToInt(), 0
-                                )
-                            }
-                            .background(Color.Red)
-                    )
-                }
+                        }
+                        .offset {
+                            IntOffset(
+                                state
+                                    .requireOffset()
+                                    .roundToInt(), 0
+                            )
+                        }
+                        .background(Color.Red)
+                )
             }
         }
 
-        val anchorA = state.anchors.positionOf(A)
-        val anchorB = state.anchors.positionOf(B)
-        val almostAnchorB = anchorB * 0.9f
-        var expectedProgress = almostAnchorB / (anchorB - anchorA)
-
-        rule.onNodeWithTag(AnchoredDraggableTestTag)
-            .performTouchInput { swipeDown(endY = almostAnchorB) }
-
-        assertThat(state.targetValue).isEqualTo(B)
-        assertThat(state.progress).isEqualTo(expectedProgress)
-
-        val almostAnchorA = anchorA + ((anchorB - anchorA) * 0.1f)
-        expectedProgress = 1 - (almostAnchorA / (anchorB - anchorA))
-
-        rule.onNodeWithTag(AnchoredDraggableTestTag)
-            .performTouchInput { swipeUp(startY = anchorB, endY = almostAnchorA) }
-
+        assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.targetValue).isEqualTo(A)
-        assertThat(state.progress).isEqualTo(expectedProgress)
+        assertThat(state.progress(from = A, to = B)).isEqualTo(0f)
+
+        scope.launch { state.animateTo(B) }
+        rule.mainClock.advanceTimeByFrame() // Start dispatching and running the animation
+
+        repeat(amountOfFramesForAnimation) { frame ->
+            val frameFraction = (frame / amountOfFramesForAnimation.toFloat())
+            val hiddenToHalfExpandedProgress = state.progress(from = A, to = B)
+            val hiddenToExpandedProgress = state.progress(from = A, to = C)
+            assertThat(hiddenToHalfExpandedProgress).isWithin(0.001f).of(frameFraction)
+            assertThat(hiddenToExpandedProgress).isWithin(0.001f).of(frameFraction / 2f)
+            rule.mainClock.advanceTimeByFrame()
+        }
+
+        rule.mainClock.autoAdvance = true
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+
+        scope.launch { state.animateTo(A) }
+        rule.mainClock.advanceTimeByFrame() // Start dispatching and running the animation
+
+        repeat(amountOfFramesForAnimation) { frame ->
+            val frameFraction = (frame / amountOfFramesForAnimation.toFloat())
+            val aToBProgress = state.progress(from = A, to = B)
+            val aToCProgress = state.progress(from = A, to = C)
+
+            assertThat(aToBProgress).isWithin(0.001f).of(1 - frameFraction)
+            assertThat(aToCProgress).isWithin(0.001f).of(0.5f - (frameFraction / 2f))
+            rule.mainClock.advanceTimeByFrame()
+        }
     }
 
     @Test
@@ -498,8 +514,9 @@
                     decayAnimationSpec = defaultDecayAnimationSpec
                 )
             }
-            LaunchedEffect(state.progress) {
-                progress = state.progress
+            val latestProgress = state.progress(from = A, to = B)
+            LaunchedEffect(latestProgress) {
+                progress = latestProgress
             }
             Box(Modifier.fillMaxSize()) {
                 Box(
@@ -880,31 +897,32 @@
     }
 
     @Test
-    fun anchoredDraggable_customDrag_updatesOffset() = runBlocking {
+    fun anchoredDraggable_customDrag_snapsToClosestAnchor() = runBlocking {
         val state = AnchoredDraggableState(
             initialValue = A,
             positionalThreshold = defaultPositionalThreshold,
             velocityThreshold = defaultVelocityThreshold,
             snapAnimationSpec = defaultAnimationSpec,
-            decayAnimationSpec = defaultDecayAnimationSpec
+            decayAnimationSpec = defaultDecayAnimationSpec,
+            anchors = DraggableAnchors {
+                A at 0f
+                B at 200f
+                C at 300f
+            }
         )
-        val anchors = DraggableAnchors {
-            A at 0f
-            B at 200f
-            C at 300f
-        }
 
-        state.updateAnchors(anchors)
         state.anchoredDrag {
             dragTo(150f)
         }
 
-        assertThat(state.requireOffset()).isEqualTo(150f)
+        assertThat(state.currentValue).isEqualTo(B)
+        assertThat(state.requireOffset()).isEqualTo(200f)
 
         state.anchoredDrag {
-            dragTo(250f)
+            dragTo(260f)
         }
-        assertThat(state.requireOffset()).isEqualTo(250f)
+        assertThat(state.currentValue).isEqualTo(C)
+        assertThat(state.requireOffset()).isEqualTo(300f)
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
index 53a5d478..a1110337 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package androidx.compose.foundation.lazy.list
 
 import androidx.compose.animation.core.snap
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/PlacedUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/PlacedUtils.kt
new file mode 100644
index 0000000..2f5acc3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/PlacedUtils.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.list
+
+import androidx.compose.ui.test.SemanticsNodeInteraction
+
+/**
+ * Asserts that the current semantics node is placed.
+ *
+ * Throws [AssertionError] if the node is not placed.
+ */
+internal fun SemanticsNodeInteraction.assertIsPlaced(): SemanticsNodeInteraction {
+    val errorMessageOnFail = "Assert failed: The component is not placed!"
+    if (!fetchSemanticsNode(errorMessageOnFail).layoutInfo.isPlaced) {
+        throw AssertionError(errorMessageOnFail)
+    }
+    return this
+}
+
+/**
+ * Asserts that the current semantics node is not placed.
+ *
+ * Throws [AssertionError] if the node is placed.
+ */
+internal fun SemanticsNodeInteraction.assertIsNotPlaced() {
+    // TODO(b/187188981): We don't have a non-throwing API to check whether an item exists.
+    //  So until this bug is fixed, we are going to catch the assertion error and then check
+    //  whether the node is placed or not.
+    try {
+        // If the node does not exist, it implies that it is also not placed.
+        assertDoesNotExist()
+    } catch (e: AssertionError) {
+        // If the node exists, we need to assert that it is not placed.
+        val errorMessageOnFail = "Assert failed: The component is placed!"
+        if (fetchSemanticsNode().layoutInfo.isPlaced) {
+            throw AssertionError(errorMessageOnFail)
+        }
+    }
+}
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 94c9612..6c18f6e 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
@@ -140,15 +140,16 @@
             }
             composeView = LocalView.current
             focusManager = LocalFocusManager.current
-            val resolvedFlingBehavior = flingBehavior ?: PagerDefaults.flingBehavior(
-                state = state,
-                pagerSnapDistance = snappingPage,
-                snapPositionalThreshold = snapPositionalThreshold
-            )
             CompositionLocalProvider(
                 LocalLayoutDirection provides config.layoutDirection,
                 LocalOverscrollConfiguration provides null
             ) {
+                val resolvedFlingBehavior = flingBehavior ?: PagerDefaults.flingBehavior(
+                    state = state,
+                    pagerSnapDistance = snappingPage,
+                    snapPositionalThreshold = snapPositionalThreshold
+                )
+
                 scope = rememberCoroutineScope()
                 Box(
                     modifier = Modifier
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerScrollingTest.kt
index b425d75..ed5fa25 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerScrollingTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.pager
 
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.snapping.MinFlingVelocityDp
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
@@ -24,6 +25,8 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
@@ -40,7 +43,7 @@
     }
 
     @Test
-    fun swipeWithLowVelocity_positionalThresholdLessThanDefaultThreshold_shouldBounceBack() =
+    fun swipeWithLowVelocity_positionalThresholdLessThanDefaultThreshold_shouldBounceBack_ltr() =
         with(rule) {
             // Arrange
             setContent {
@@ -90,6 +93,57 @@
         }
 
     @Test
+    fun swipeWithLowVelocity_positionalThresholdLessThanDefaultThreshold_shouldBounceBack_rtl() =
+        with(rule) {
+            // Arrange
+            setContent {
+                ParameterizedPager(
+                    initialPage = 5,
+                    modifier = Modifier.fillMaxSize(),
+                    orientation = it.orientation,
+                    pageSpacing = it.pageSpacing,
+                    layoutDirection = it.layoutDirection
+                )
+            }
+            val ParamsWithRtl = ParamsToTest.map { it.copy(layoutDirection = LayoutDirection.Rtl) }
+            forEachParameter(ParamsWithRtl) { param ->
+                val swipeValue = 0.4f
+                val delta = pagerSize * swipeValue
+
+                // Act - forward
+                onPager().performTouchInput {
+                    val (start, end) = if (param.orientation == Orientation.Vertical) {
+                        topCenter to topCenter.copy(y = topCenter.y + delta)
+                    } else {
+                        centerRight to centerRight.copy(x = centerRight.x - delta)
+                    }
+                    swipeWithVelocity(start, end, 0.5f * MinFlingVelocityDp.toPx())
+                }
+                waitForIdle()
+
+                // Assert
+                onNodeWithTag("5").assertIsDisplayed()
+                param.confirmPageIsInCorrectPosition(5)
+
+                // Act - backward
+                onPager().performTouchInput {
+                    val (start, end) = if (param.orientation == Orientation.Vertical) {
+                        topCenter to topCenter.copy(y = topCenter.y - delta)
+                    } else {
+                        centerRight to centerRight.copy(x = centerRight.x + delta)
+                    }
+                    swipeWithVelocity(start, end, 0.5f * MinFlingVelocityDp.toPx())
+                }
+                waitForIdle()
+
+                // Assert
+                onNodeWithTag("5").assertIsDisplayed()
+                param.confirmPageIsInCorrectPosition(5)
+                resetTestCase(5)
+            }
+        }
+
+    @Test
     fun swipeWithLowVelocity_positionalThresholdLessThanLowThreshold_shouldBounceBack() =
         with(rule) {
             // Arrange
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/SingleParamBasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/SingleParamBasePagerTest.kt
index 9c86107..2e565132 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/SingleParamBasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/SingleParamBasePagerTest.kt
@@ -114,15 +114,17 @@
         }
         composeView = LocalView.current
         focusManager = LocalFocusManager.current
-        val resolvedFlingBehavior = flingBehavior ?: PagerDefaults.flingBehavior(
-            state = state,
-            pagerSnapDistance = snappingPage,
-            snapPositionalThreshold = snapPositionalThreshold
-        )
+
         CompositionLocalProvider(
             LocalLayoutDirection provides layoutDirection,
             LocalOverscrollConfiguration provides null
         ) {
+            val resolvedFlingBehavior = flingBehavior ?: PagerDefaults.flingBehavior(
+                state = state,
+                pagerSnapDistance = snappingPage,
+                snapPositionalThreshold = snapPositionalThreshold
+            )
+
             scope = rememberCoroutineScope()
             Box(
                 modifier = Modifier
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextMinTouchBoundsSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextMinTouchBoundsSelectionGesturesTest.kt
index 4fecf01..73d0276 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextMinTouchBoundsSelectionGesturesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextMinTouchBoundsSelectionGesturesTest.kt
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.text.selection.gestures.MultiTextMinTouchBoundsSelectionGesturesTest.TestVertical.OVERLAP_BELONGS_TO_FIRST
 import androidx.compose.foundation.text.selection.gestures.MultiTextMinTouchBoundsSelectionGesturesTest.TestVertical.OVERLAP_BELONGS_TO_SECOND
 import androidx.compose.foundation.text.selection.gestures.MultiTextMinTouchBoundsSelectionGesturesTest.TestVertical.OVERLAP_EQUIDISTANT
+import androidx.compose.foundation.text.selection.gestures.util.longPress
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.mutableStateOf
@@ -87,22 +88,30 @@
      */
     private val touchTargetDpLen = dpLen + 12.dp * 2
 
-    enum class TestHorizontal(val x: Float) {
-        LEFT(-6f),
-        CENTER(10f),
-        RIGHT(26f)
+    enum class TestHorizontal(
+        val x: Float,
+        /** The x-value we would coerce to in order to get the x coordinate onto a box. */
+        val coercedX: Float,
+    ) {
+        LEFT(x = -6f, coercedX = 1f),
+        CENTER(x = 10f, coercedX = 10f),
+        RIGHT(x = 26f, coercedX = 19f)
     }
 
-    enum class TestVertical(val y: Float) {
-        ABOVE(-6f),
-        ON_FIRST(10f),
-        NO_OVERLAP_BELONGS_TO_FIRST(25f),
-        OVERLAP_BELONGS_TO_FIRST(29f),
-        OVERLAP_EQUIDISTANT(30f),
-        OVERLAP_BELONGS_TO_SECOND(31f),
-        NO_OVERLAP_BELONGS_TO_SECOND(35f),
-        ON_SECOND(50f),
-        BELOW(66f),
+    enum class TestVertical(
+        val y: Float,
+        /** The y-value we would coerce to in order to get the y coordinate onto a box. */
+        val coercedY: Float,
+    ) {
+        ABOVE(y = -6f, coercedY = 1f),
+        ON_FIRST(y = 10f, coercedY = 10f),
+        NO_OVERLAP_BELONGS_TO_FIRST(y = 25f, coercedY = 19f),
+        OVERLAP_BELONGS_TO_FIRST(y = 29f, coercedY = 19f),
+        OVERLAP_EQUIDISTANT(y = 30f, coercedY = 19f),
+        OVERLAP_BELONGS_TO_SECOND(y = 31f, coercedY = 41f),
+        NO_OVERLAP_BELONGS_TO_SECOND(y = 35f, coercedY = 41f),
+        ON_SECOND(y = 50f, coercedY = 50f),
+        BELOW(y = 66f, coercedY = 59f);
     }
 
     enum class ExpectedText(val selectableId: Long?) {
@@ -170,8 +179,24 @@
     }
 
     @Test
-    fun minTouchTargetSelectionGestureTest() {
+    fun minTouchTargetSelectionGestureTest() = runTest {
         performTouchGesture { longClick(Offset(horizontal.x, vertical.y)) }
+    }
+
+    // Regression test for b/325307463
+    @Test
+    fun dragIntoMinTouchTargetSelectionGestureTest() = runTest {
+        performTouchGesture {
+            longPress(Offset(horizontal.coercedX, vertical.coercedY))
+            // The crash involved a quick drag from on the text to off the text
+            // causing a race of some state not being set before the drag is executed,
+            // so we want to force the moveTo immediately after the long press finishes.
+            moveTo(Offset(horizontal.x, vertical.y), delayMillis = 0L)
+        }
+    }
+
+    fun runTest(block: () -> Unit) {
+        block()
 
         val expectedSelectableId = expectedText.selectableId
         if (expectedSelectableId == null) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index 9766e18..5ca3dbde 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -33,7 +33,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
@@ -47,14 +46,15 @@
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.node.LayoutModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.SemanticsModifierNode
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.ScrollAxisRange
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.getScrollViewportLength
 import androidx.compose.ui.semantics.horizontalScrollAxisRange
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.scrollBy
-import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.verticalScrollAxisRange
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.util.fastRoundToInt
@@ -274,40 +274,16 @@
     isVertical: Boolean
 ) = composed(
     factory = {
-        val coroutineScope = rememberCoroutineScope()
-        semantics {
-            isTraversalGroup = true
-            val accessibilityScrollState = ScrollAxisRange(
-                value = { state.value.toFloat() },
-                maxValue = { state.maxValue.toFloat() },
-                reverseScrolling = reverseScrolling
-            )
-            if (isVertical) {
-                this.verticalScrollAxisRange = accessibilityScrollState
-            } else {
-                this.horizontalScrollAxisRange = accessibilityScrollState
-            }
-            if (isScrollable) {
-                // when b/156389287 is fixed, this should be proper scrollTo with reverse handling
-                scrollBy(
-                    action = { x: Float, y: Float ->
-                        coroutineScope.launch {
-                            if (isVertical) {
-                                (state as ScrollableState).animateScrollBy(y)
-                            } else {
-                                (state as ScrollableState).animateScrollBy(x)
-                            }
-                        }
-                        return@scrollBy true
-                    }
+        Modifier
+            .then(
+                ScrollSemanticsElement(
+                    state = state,
+                    reverseScrolling = reverseScrolling,
+                    flingBehavior = flingBehavior,
+                    isScrollable = isScrollable,
+                    isVertical = isVertical,
                 )
-
-                getScrollViewportLength {
-                    it.add(state.viewportSize.toFloat())
-                    true
-                }
-            }
-        }
+            )
             .scrollingContainer(
                 state = state,
                 orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
@@ -328,6 +304,76 @@
     }
 )
 
+private data class ScrollSemanticsElement(
+    val state: ScrollState,
+    val reverseScrolling: Boolean,
+    val flingBehavior: FlingBehavior?,
+    val isScrollable: Boolean,
+    val isVertical: Boolean
+) : ModifierNodeElement<ScrollSemanticsModifierNode>() {
+    override fun create(): ScrollSemanticsModifierNode = ScrollSemanticsModifierNode(
+        state = state,
+        reverseScrolling = reverseScrolling,
+        flingBehavior = flingBehavior,
+        isScrollable = isScrollable,
+        isVertical = isVertical,
+    )
+
+    override fun update(node: ScrollSemanticsModifierNode) {
+        node.state = state
+        node.reverseScrolling = reverseScrolling
+        node.flingBehavior = flingBehavior
+        node.isScrollable = isScrollable
+        node.isVertical = isVertical
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // Not a public modifier.
+    }
+}
+
+private class ScrollSemanticsModifierNode(
+    var state: ScrollState,
+    var reverseScrolling: Boolean,
+    var flingBehavior: FlingBehavior?,
+    var isScrollable: Boolean,
+    var isVertical: Boolean
+) : Modifier.Node(), SemanticsModifierNode {
+    override fun SemanticsPropertyReceiver.applySemantics() {
+        isTraversalGroup = true
+        val accessibilityScrollState = ScrollAxisRange(
+            value = { state.value.toFloat() },
+            maxValue = { state.maxValue.toFloat() },
+            reverseScrolling = reverseScrolling
+        )
+        if (isVertical) {
+            this.verticalScrollAxisRange = accessibilityScrollState
+        } else {
+            this.horizontalScrollAxisRange = accessibilityScrollState
+        }
+        if (isScrollable) {
+            // when b/156389287 is fixed, this should be proper scrollTo with reverse handling
+            scrollBy(
+                action = { x: Float, y: Float ->
+                    coroutineScope.launch {
+                        if (isVertical) {
+                            (state as ScrollableState).animateScrollBy(y)
+                        } else {
+                            (state as ScrollableState).animateScrollBy(x)
+                        }
+                    }
+                    return@scrollBy true
+                }
+            )
+
+            getScrollViewportLength {
+                it.add(state.viewportSize.toFloat())
+                true
+            }
+        }
+    }
+}
+
 internal class ScrollingLayoutElement(
     val scrollState: ScrollState,
     val isReversed: Boolean,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index b6abe95..0d055ac 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -49,6 +49,8 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Velocity
 import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
 import kotlin.math.sign
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
@@ -303,26 +305,24 @@
     private var state: AnchoredDraggableState<T>,
     private var orientation: Orientation,
     enabled: Boolean,
-    reverseDirection: Boolean,
+    private var reverseDirection: Boolean,
     interactionSource: MutableInteractionSource?,
     private var overscrollEffect: OverscrollEffect?,
-    startDragImmediately: () -> Boolean
-) : AbstractDraggableNode(
+    private var startDragImmediately: () -> Boolean
+) : DragGestureNode(
     canDrag = AlwaysDrag,
     enabled = enabled,
-    interactionSource = interactionSource,
-    startDragImmediately = startDragImmediately,
-    reverseDirection = reverseDirection
+    interactionSource = interactionSource
 ) {
 
     override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
         state.anchoredDrag(MutatePriority.Default) {
             forEachDelta { dragDelta ->
                 if (overscrollEffect == null) {
-                    dragTo(state.newOffsetForDelta(dragDelta.delta.toFloat()))
+                    dragTo(state.newOffsetForDelta(dragDelta.delta.reverseIfNeeded().toFloat()))
                 } else {
                     overscrollEffect!!.applyToScroll(
-                        delta = dragDelta.delta,
+                        delta = dragDelta.delta.reverseIfNeeded(),
                         source = NestedScrollSource.Drag
                     ) { deltaForDrag ->
                         val dragOffset = state.newOffsetForDelta(deltaForDrag.toFloat())
@@ -342,10 +342,10 @@
 
     override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) {
         if (overscrollEffect == null) {
-            state.settle(velocity.toFloat()).toVelocity()
+            state.settle(velocity.reverseIfNeeded().toFloat()).toVelocity()
         } else {
             overscrollEffect!!.applyToFling(
-                velocity = velocity
+                velocity = velocity.reverseIfNeeded()
             ) { availableVelocity ->
                 val consumed = state.settle(availableVelocity.toFloat()).toVelocity()
                 val currentOffset = state.requireOffset()
@@ -361,6 +361,8 @@
         }
     }
 
+    override fun startDragImmediately(): Boolean = startDragImmediately.invoke()
+
     fun update(
         state: AnchoredDraggableState<T>,
         orientation: Orientation,
@@ -381,13 +383,17 @@
             resetPointerInputHandling = true
         }
 
+        if (this.reverseDirection != reverseDirection) {
+            this.reverseDirection = reverseDirection
+            resetPointerInputHandling = true
+        }
+
         this.overscrollEffect = overscrollEffect
+        this.startDragImmediately = startDragImmediately
 
         update(
             enabled = enabled,
             interactionSource = interactionSource,
-            startDragImmediately = startDragImmediately,
-            reverseDirection = reverseDirection,
             isResetPointerInputHandling = resetPointerInputHandling,
         )
     }
@@ -407,6 +413,9 @@
 
     private fun Offset.toFloat() =
         if (orientation == Orientation.Vertical) this.y else this.x
+
+    private fun Velocity.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
+    private fun Offset.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
 }
 
 private val AlwaysDrag: (PointerInputChange) -> Boolean = { true }
@@ -488,11 +497,22 @@
 
     /**
      * The current value of the [AnchoredDraggableState].
+     *
+     * That is the closest anchor point that the state has passed through.
      */
     var currentValue: T by mutableStateOf(initialValue)
         private set
 
     /**
+     * The value the [AnchoredDraggableState] is currently settled at.
+     *
+     * When progressing through multiple anchors, e.g. `A -> B -> C`, [settledValue] will stay the
+     * same until settled at an anchor, while [currentValue] will update to the closest anchor.
+     */
+    var settledValue: T by mutableStateOf(initialValue)
+        private set
+
+    /**
      * The target value. This is the closest value to the current offset, taking into account
      * positional thresholds. If no interactions like animations or drags are in progress, this
      * will be the current value.
@@ -507,20 +527,6 @@
     }
 
     /**
-     * The closest value in the swipe direction from the current offset, not considering thresholds.
-     * If an [anchoredDrag] is in progress, this will be the target of that anchoredDrag (if
-     * specified).
-     */
-    internal val closestValue: T by derivedStateOf {
-        dragTarget ?: run {
-            val currentOffset = offset
-            if (!currentOffset.isNaN()) {
-                computeTargetWithoutThresholds(currentOffset, currentValue)
-            } else currentValue
-        }
-    }
-
-    /**
      * The current offset, or [Float.NaN] if it has not been initialized yet.
      *
      * The offset will be initialized when the anchors are first set through [updateAnchors].
@@ -552,13 +558,39 @@
     val isAnimationRunning: Boolean get() = dragTarget != null
 
     /**
-     * The fraction of the progress going from [currentValue] to [closestValue], within [0f..1f]
+     * The fraction of the offset between [from] and [to], as a fraction between [0f..1f], or 1f if
+     * [from] is equal to [to].
+     *
+     * @param from The starting value used to calculate the distance
+     * @param to The end value used to calculate the distance
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    fun progress(from: T, to: T): Float {
+        val fromOffset = anchors.positionOf(from)
+        val toOffset = anchors.positionOf(to)
+        val currentOffset = offset.coerceIn(
+            min(fromOffset, toOffset), // fromOffset might be > toOffset
+            max(fromOffset, toOffset)
+        )
+        val fraction = (currentOffset - fromOffset) / (toOffset - fromOffset)
+        return if (!fraction.isNaN()) {
+            // If we are very close to 0f or 1f, we round to the closest
+            if (fraction < 1e-6f) 0f else if (fraction > 1 - 1e-6f) 1f else abs(fraction)
+        } else 1f
+    }
+
+    /**
+     * The fraction of the progress going from [settledValue] to [targetValue], within [0f..1f]
      * bounds, or 1f if the [AnchoredDraggableState] is in a settled state.
      */
+    @Deprecated(
+        message = "Use the progress function to query the progress between two specified " +
+            "anchors.",
+        replaceWith = ReplaceWith("progress(state.settledValue, state.targetValue)"))
     @get:FloatRange(from = 0.0, to = 1.0)
     val progress: Float by derivedStateOf(structuralEqualityPolicy()) {
-        val a = anchors.positionOf(currentValue)
-        val b = anchors.positionOf(closestValue)
+        val a = anchors.positionOf(settledValue)
+        val b = anchors.positionOf(targetValue)
         val distance = abs(b - a)
         if (!distance.isNaN() && distance > 1e-6f) {
             val progress = (this.requireOffset() - a) / (b - a)
@@ -674,26 +706,55 @@
         }
     }
 
-    private fun computeTargetWithoutThresholds(
-        offset: Float,
-        currentValue: T,
-    ): T {
-        val currentAnchors = anchors
-        val currentAnchor = currentAnchors.positionOf(currentValue)
-        return if (currentAnchor == offset || currentAnchor.isNaN()) {
-            currentValue
-        } else {
-            currentAnchors.closestAnchor(
-                offset,
-                offset - currentAnchor > 0
-            ) ?: currentValue
-        }
-    }
+    private var nextValue: T by mutableStateOf(initialValue)
 
     private val anchoredDragScope: AnchoredDragScope = object : AnchoredDragScope {
+        var initialized = false
+        var absoluteThresholdToCross: Float = Float.NaN
+        var min = Float.NaN
+        var max = Float.NaN
+
         override fun dragTo(newOffset: Float, lastKnownVelocity: Float) {
+            val previousOffset = offset
             offset = newOffset
             lastVelocity = lastKnownVelocity
+            val isMovingForward = newOffset >= previousOffset
+            if (initialized) {
+                val crossedThresholdTowardsNextAnchor = if (isMovingForward) {
+                    newOffset >= absoluteThresholdToCross
+                } else {
+                    newOffset <= absoluteThresholdToCross
+                }
+                if (crossedThresholdTowardsNextAnchor) {
+                    update(isMovingForward)
+                }
+            } else if (!previousOffset.isNaN()) {
+                // In the first invocation, we do not have a direction. The previous offset will be
+                // NaN in the first invocation of dragTo; so we will only initialize in the second
+                // invocation when we have a direction to calculate the thresholds with
+                update(isMovingForward)
+                initialized = true
+            }
+        }
+
+        fun update(isMovingForward: Boolean) {
+            val currentAnchorPosition = anchors.positionOf(currentValue)
+            min = anchors.minAnchor()
+            max = anchors.maxAnchor()
+            val lookUpwards = when (currentAnchorPosition) {
+                min -> true
+                max -> false
+                else -> isMovingForward
+            }
+            val closestAnchor = anchors.closestAnchor(offset, lookUpwards)
+            val nextAnchor = closestAnchor ?: currentValue
+            val nextAnchorPosition = anchors.positionOf(nextAnchor!!)
+            if (confirmValueChange(nextAnchor)) {
+                currentValue = nextAnchor
+            }
+            val relativeThreshold = (nextAnchorPosition - currentAnchorPosition) / 2f
+            absoluteThresholdToCross = currentAnchorPosition + relativeThreshold
+            nextValue = nextAnchor
         }
     }
 
@@ -719,18 +780,15 @@
         dragPriority: MutatePriority = MutatePriority.Default,
         block: suspend AnchoredDragScope.(anchors: DraggableAnchors<T>) -> Unit
     ) {
-        try {
-            dragMutex.mutate(dragPriority) {
-                restartable(inputs = { anchors }) { latestAnchors ->
-                    anchoredDragScope.block(latestAnchors)
-                }
+        dragMutex.mutate(dragPriority) {
+            restartable(inputs = { anchors }) { latestAnchors ->
+                anchoredDragScope.block(latestAnchors)
             }
-        } finally {
             val closest = anchors.closestAnchor(offset)
-            if (closest != null &&
-                abs(offset - anchors.positionOf(closest)) <= 0.5f &&
-                confirmValueChange.invoke(closest)
-            ) {
+            if (closest != null && confirmValueChange.invoke(closest)) {
+                val closestAnchorOffset = anchors.positionOf(closest)
+                anchoredDragScope.dragTo(closestAnchorOffset, lastVelocity)
+                settledValue = closest
                 currentValue = closest
             }
         }
@@ -762,7 +820,7 @@
     suspend fun anchoredDrag(
         targetValue: T,
         dragPriority: MutatePriority = MutatePriority.Default,
-        block: suspend AnchoredDragScope.(anchors: DraggableAnchors<T>, targetValue: T) -> Unit
+        block: suspend AnchoredDragScope.(anchor: DraggableAnchors<T>, targetValue: T) -> Unit
     ) {
         if (anchors.hasAnchorFor(targetValue)) {
             try {
@@ -770,23 +828,24 @@
                     dragTarget = targetValue
                     restartable(
                         inputs = { anchors to this@AnchoredDraggableState.targetValue }
-                    ) { (latestAnchors, latestTarget) ->
-                        anchoredDragScope.block(latestAnchors, latestTarget)
+                    ) { (anchors, latestTarget) ->
+                        anchoredDragScope.block(anchors, latestTarget)
+                    }
+                    if (confirmValueChange(targetValue)) {
+                        val latestTargetOffset = anchors.positionOf(targetValue)
+                        anchoredDragScope.dragTo(latestTargetOffset, lastVelocity)
+                        settledValue = targetValue
+                        currentValue = targetValue
                     }
                 }
             } finally {
                 dragTarget = null
-                val closest = anchors.closestAnchor(offset)
-                if (closest != null &&
-                    abs(offset - anchors.positionOf(closest)) <= 0.5f &&
-                    confirmValueChange.invoke(closest)
-                ) {
-                    currentValue = closest
-                }
             }
         } else {
-            // Todo: b/283467401, revisit this behavior
-            currentValue = targetValue
+            if (confirmValueChange(targetValue)) {
+                settledValue = targetValue
+                currentValue = targetValue
+            }
         }
     }
 
@@ -821,6 +880,7 @@
                 dragTarget = null
             }
             currentValue = targetValue
+            settledValue = targetValue
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 59d6088..1b92b0e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -298,22 +298,20 @@
     private var orientation: Orientation,
     enabled: Boolean,
     interactionSource: MutableInteractionSource?,
-    startDragImmediately: () -> Boolean,
+    private var startDragImmediately: () -> Boolean,
     private var onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
     private var onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit,
-    reverseDirection: Boolean
-) : AbstractDraggableNode(
+    private var reverseDirection: Boolean
+) : DragGestureNode(
     canDrag,
     enabled,
-    interactionSource,
-    startDragImmediately,
-    reverseDirection
+    interactionSource
 ) {
 
     override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
         state.drag(MutatePriority.UserInput) {
             forEachDelta { dragDelta ->
-                dragBy(dragDelta.delta.toFloat(orientation))
+                dragBy(dragDelta.delta.reverseIfNeeded().toFloat(orientation))
             }
         }
     }
@@ -324,7 +322,9 @@
         this@DraggableNode.onDragStarted(this, startedPosition)
 
     override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) =
-        this@DraggableNode.onDragStopped(this, velocity.toFloat(orientation))
+        this@DraggableNode.onDragStopped(this, velocity.reverseIfNeeded().toFloat(orientation))
+
+    override fun startDragImmediately(): Boolean = startDragImmediately.invoke()
 
     fun update(
         state: DraggableState,
@@ -346,33 +346,40 @@
             this.orientation = orientation
             resetPointerInputHandling = true
         }
+        if (this.reverseDirection != reverseDirection) {
+            this.reverseDirection = reverseDirection
+            resetPointerInputHandling = true
+        }
+
         this.onDragStarted = onDragStarted
         this.onDragStopped = onDragStopped
+        this.startDragImmediately = startDragImmediately
 
         update(
             canDrag,
             enabled,
             interactionSource,
-            startDragImmediately,
-            reverseDirection,
             resetPointerInputHandling
         )
     }
+
+    private fun Velocity.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
+    private fun Offset.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
 }
 
-internal abstract class AbstractDraggableNode(
+/**
+ * A node that performs drag gesture recognition and event propagation.
+ */
+internal abstract class DragGestureNode(
     private var canDrag: (PointerInputChange) -> Boolean,
     private var enabled: Boolean,
     private var interactionSource: MutableInteractionSource?,
-    private var startDragImmediately: () -> Boolean,
-    private var reverseDirection: Boolean
 ) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode {
 
     // Use wrapper lambdas here to make sure that if these properties are updated while we suspend,
     // we point to the new reference when we invoke them. startDragImmediately is a lambda since we
     // need the most recent value passed to it from Scrollable.
     private val _canDrag: (PointerInputChange) -> Boolean = { canDrag(it) }
-    private val _startDragImmediately: () -> Boolean = { startDragImmediately() }
     private val velocityTracker = VelocityTracker()
     private var isListeningForEvents = false
 
@@ -402,6 +409,12 @@
      */
     abstract suspend fun CoroutineScope.onDragStopped(velocity: Velocity)
 
+    /**
+     * If touch slop recognition should be skipped. If this is true, this node will start
+     * recognizing drag events immediately without waiting for touch slop.
+     */
+    abstract fun startDragImmediately(): Boolean
+
     private fun startListeningForEvents() {
         isListeningForEvents = true
 
@@ -445,7 +458,7 @@
                     while (isActive) {
                         awaitDownAndSlop(
                             _canDrag,
-                            _startDragImmediately,
+                            ::startDragImmediately,
                             velocityTracker,
                             pointerDirectionConfig
                         )?.let {
@@ -462,8 +475,7 @@
                                     it.first,
                                     it.second,
                                     velocityTracker,
-                                    channel,
-                                    reverseDirection
+                                    channel
                                 ) { event ->
                                     pointerDirectionConfig.calculateDeltaChange(
                                         event.positionChangeIgnoreConsumed()
@@ -480,7 +492,7 @@
                                         Velocity(maximumVelocity, maximumVelocity)
                                     )
                                     velocityTracker.resetTracking()
-                                    DragStopped(velocity * if (reverseDirection) -1f else 1f)
+                                    DragStopped(velocity)
                                 } else {
                                     DragCancelled
                                 }
@@ -554,8 +566,6 @@
         canDrag: (PointerInputChange) -> Boolean = this.canDrag,
         enabled: Boolean = this.enabled,
         interactionSource: MutableInteractionSource? = this.interactionSource,
-        startDragImmediately: () -> Boolean = this.startDragImmediately,
-        reverseDirection: Boolean = this.reverseDirection,
         isResetPointerInputHandling: Boolean = false
     ) {
         var resetPointerInputHandling = isResetPointerInputHandling
@@ -571,11 +581,7 @@
             disposeInteractionSource()
             this.interactionSource = interactionSource
         }
-        this.startDragImmediately = startDragImmediately
-        if (this.reverseDirection != reverseDirection) {
-            this.reverseDirection = reverseDirection
-            resetPointerInputHandling = true
-        }
+
         if (resetPointerInputHandling) {
             pointerInputNode.resetPointerInputHandler()
         }
@@ -623,7 +629,6 @@
     initialDelta: Offset,
     velocityTracker: VelocityTracker,
     channel: SendChannel<DragEvent>,
-    reverseDirection: Boolean,
     hasDragged: (PointerInputChange) -> Boolean,
 ): Boolean {
 
@@ -634,7 +639,7 @@
         Offset(overSlopOffset.x * xSign, overSlopOffset.y * ySign)
     channel.trySend(DragStarted(adjustedStart))
 
-    channel.trySend(DragDelta(if (reverseDirection) initialDelta * -1f else initialDelta))
+    channel.trySend(DragDelta(initialDelta))
 
     return onDragOrUp(hasDragged, startEvent.id) { event ->
         // Velocity tracker takes all events, even UP
@@ -644,7 +649,7 @@
         if (!event.changedToUpIgnoreConsumed()) {
             val delta = event.positionChange()
             event.consume()
-            channel.trySend(DragDelta(if (reverseDirection) delta * -1f else delta))
+            channel.trySend(DragDelta(delta))
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
index e89f4ab..c8e26da 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
@@ -192,7 +192,7 @@
         CanDrag,
         enabled,
         interactionSource,
-        if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
+        startDragImmediately,
         onDragStarted,
         onDragStopped,
         reverseDirection
@@ -204,7 +204,7 @@
             CanDrag,
             enabled,
             interactionSource,
-            if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
+            startDragImmediately,
             onDragStarted,
             onDragStopped,
             reverseDirection
@@ -252,8 +252,6 @@
     }
 
     companion object {
-        val StartDragImmediately = { true }
-        val DoNotStartDragImmediately = { false }
         val CanDrag: (PointerInputChange) -> Boolean = { true }
     }
 }
@@ -264,16 +262,14 @@
     canDrag: (PointerInputChange) -> Boolean,
     enabled: Boolean,
     interactionSource: MutableInteractionSource?,
-    startDragImmediately: () -> Boolean,
+    private var startDragImmediately: Boolean,
     private var onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
     private var onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit,
-    reverseDirection: Boolean
-) : AbstractDraggableNode(
+    private var reverseDirection: Boolean
+) : DragGestureNode(
     canDrag,
     enabled,
-    interactionSource,
-    startDragImmediately,
-    reverseDirection
+    interactionSource
 ) {
 
     override suspend fun drag(
@@ -281,7 +277,7 @@
     ) {
         state.drag(MutatePriority.UserInput) {
             forEachDelta { dragDelta ->
-                dragBy(dragDelta.delta)
+                dragBy(dragDelta.delta.reverseIfNeeded())
             }
         }
     }
@@ -292,14 +288,16 @@
         this@Draggable2DNode.onDragStarted(this, startedPosition)
 
     override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) =
-        this@Draggable2DNode.onDragStopped(this, velocity)
+        this@Draggable2DNode.onDragStopped(this, velocity.reverseIfNeeded())
+
+    override fun startDragImmediately(): Boolean = startDragImmediately
 
     fun update(
         state: Draggable2DState,
         canDrag: (PointerInputChange) -> Boolean,
         enabled: Boolean,
         interactionSource: MutableInteractionSource?,
-        startDragImmediately: () -> Boolean,
+        startDragImmediately: Boolean,
         onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
         onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit,
         reverseDirection: Boolean
@@ -309,18 +307,25 @@
             this.state = state
             resetPointerInputHandling = true
         }
+        if (this.reverseDirection != reverseDirection) {
+            this.reverseDirection = reverseDirection
+            resetPointerInputHandling = true
+        }
+
         this.onDragStarted = onDragStarted
         this.onDragStopped = onDragStopped
+        this.startDragImmediately = startDragImmediately
 
         update(
             canDrag,
             enabled,
             interactionSource,
-            startDragImmediately,
-            reverseDirection,
             resetPointerInputHandling
         )
     }
+
+    private fun Velocity.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
+    private fun Offset.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
 }
 
 @OptIn(ExperimentalFoundationApi::class)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
index b77ca7d..4fa1ead 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.pager.PagerSnapDistance
 import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.mainAxisViewportSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
 import kotlin.math.abs
 import kotlin.math.absoluteValue
@@ -186,8 +187,12 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
-private fun PagerState.isScrollingForward() = dragGestureDelta() < 0
+private fun PagerState.isLtrDragging() = dragGestureDelta() > 0
+private fun PagerState.isScrollingForward(): Boolean {
+    val reverseScrollDirection = layoutInfo.reverseLayout
+    return (isLtrDragging() && reverseScrollDirection ||
+        !isLtrDragging() && !reverseScrollDirection)
+}
 
 @OptIn(ExperimentalFoundationApi::class)
 private fun PagerState.dragGestureDelta() = if (layoutInfo.orientation == Orientation.Horizontal) {
@@ -209,16 +214,27 @@
 @OptIn(ExperimentalFoundationApi::class)
 internal fun calculateFinalSnappingBound(
     pagerState: PagerState,
+    layoutDirection: LayoutDirection,
     snapPositionalThreshold: Float,
     flingVelocity: Float,
     lowerBoundOffset: Float,
     upperBoundOffset: Float
 ): Float {
 
-    val isForward = pagerState.isScrollingForward()
-
-    debugLog { "isForward=$isForward" }
-
+    val isForward = if (pagerState.layoutInfo.orientation == Orientation.Vertical) {
+        pagerState.isScrollingForward()
+    } else {
+        if (layoutDirection == LayoutDirection.Ltr) {
+            pagerState.isScrollingForward()
+        } else {
+            !pagerState.isScrollingForward()
+        }
+    }
+    debugLog {
+        "isLtrDragging=${pagerState.isLtrDragging()} " +
+            "isForward=$isForward " +
+            "layoutDirection=$layoutDirection"
+    }
     // how many pages have I scrolled using a drag gesture.
     val offsetFromSnappedPosition =
         pagerState.dragGestureDelta() / pagerState.layoutInfo.pageSize.toFloat()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 5cc47d2..26dce70 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.pageDown
 import androidx.compose.ui.semantics.pageLeft
 import androidx.compose.ui.semantics.pageRight
@@ -300,12 +301,14 @@
                 "You've specified $snapPositionalThreshold"
         }
         val density = LocalDensity.current
+        val layoutDirection = LocalLayoutDirection.current
         return remember(
             state,
             decayAnimationSpec,
             snapAnimationSpec,
             pagerSnapDistance,
-            density
+            density,
+            layoutDirection
         ) {
             val snapLayoutInfoProvider =
                 SnapLayoutInfoProvider(
@@ -315,6 +318,7 @@
                 ) { flingVelocity, lowerBound, upperBound ->
                     calculateFinalSnappingBound(
                         pagerState = state,
+                        layoutDirection = layoutDirection,
                         snapPositionalThreshold = snapPositionalThreshold,
                         flingVelocity = flingVelocity,
                         lowerBoundOffset = lowerBound,
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 5808ef2..e18450f 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
@@ -98,7 +98,19 @@
      * change. The expectation is that this callback will end up causing `setSelection` to get
      * called. This is what makes this a "controlled component".
      */
-    var onSelectionChange: (Selection?) -> Unit = {}
+    var onSelectionChange: (Selection?) -> Unit = { selection = it }
+        set(newOnSelectionChange) {
+            // Wrap the given lambda with one that sets the selection immediately.
+            // The onSelectionChange loop requires a composition to happen for the selection
+            // to be updated, so we want to shorten that loop for gesture use cases where
+            // multiple selection changing events can be acted on within a single composition
+            // loop. Previous selection is used as part of that loop so keeping it up to date
+            // is important.
+            field = { newSelection ->
+                selection = newSelection
+                newOnSelectionChange(newSelection)
+            }
+        }
 
     /**
      * [HapticFeedback] handle to perform haptic feedback.
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/adding/AddingToProject.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/adding/AddingToProject.kt
deleted file mode 100644
index 4353eff..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/adding/AddingToProject.kt
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "ClassName"
-)
-
-package androidx.compose.integration.docs.adding
-
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.test.platform.app.InstrumentationRegistry
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop/adding
- *
- * No action required if it's modified.
- */
-
-private object AddingToProjectSnippet1 {
-
-    class MyActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            // ...
-
-            val greeting = findViewById<ComposeView>(R.id.greeting)
-            greeting.setContent {
-                MdcTheme { // or AppCompatTheme
-                    Greeting()
-                }
-            }
-        }
-    }
-
-    @Composable
-    private fun Greeting() {
-        Text(
-            text = stringResource(R.string.greeting),
-            style = MaterialTheme.typography.h5,
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(horizontal = dimensionResource(R.dimen.margin_small))
-                .wrapContentWidth(Alignment.CenterHorizontally)
-        )
-    }
-}
-
-private object AddingToProjectSnippet2 {
-
-    class MyActivityTest {
-        @Rule
-        @JvmField
-        val composeTestRule = createAndroidComposeRule<MyActivity>()
-
-        @Test
-        fun testGreeting() {
-            val greeting = InstrumentationRegistry.getInstrumentation()
-                .targetContext.resources.getString(R.string.greeting)
-
-            composeTestRule.onNodeWithText(greeting).assertIsDisplayed()
-        }
-    }
-}
-
-private object AddingToProjectSnippet3 {
-
-    class MyActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            setContent {
-                MyScreen()
-            }
-        }
-    }
-
-    @Composable
-    private fun MyScreen(
-        viewModel: MyViewModel = viewModel()
-    ) {
-        val uiState by viewModel.uiState.collectAsState()
-        when {
-            uiState.isLoading -> { /* ... */ }
-            uiState.isSuccess -> { /* ... */ }
-            uiState.isError -> { /* ... */ }
-        }
-    }
-
-    class MyViewModel : ViewModel() {
-        private val _uiState = MutableStateFlow(MyScreenState.Loading)
-        val uiState: StateFlow<MyScreenState> = _uiState
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private object R {
-    object dimen {
-        const val margin_small = 1
-    }
-    object id {
-        const val greeting = 2
-    }
-    object string {
-        const val greeting = 3
-    }
-}
-
-@Composable
-private fun MdcTheme(content: @Composable () -> Unit) {
-}
-private class MyActivity : AppCompatActivity()
-private class MyScreenState {
-    val isLoading = true
-    val isSuccess = true
-    val isError = true
-    companion object {
-        val Loading = MyScreenState()
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/input/HandlingInteraction.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/input/HandlingInteraction.kt
deleted file mode 100644
index b26842a..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/input/HandlingInteraction.kt
+++ /dev/null
@@ -1,166 +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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER")
-
-package androidx.compose.integration.docs.input
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.Interaction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.PressInteraction
-import androidx.compose.foundation.interaction.collectIsPressedAsState
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.size
-import androidx.compose.integration.docs.input.DynamicButton.PressIconButton
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Icon
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ShoppingCart
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/handling-interaction
- *
- * No action required if it's modified.
- */
-
-@Composable
-private fun UseInteractionSoure() {
-    val interactionSource = remember { MutableInteractionSource() }
-    val isPressed by interactionSource.collectIsPressedAsState()
-
-    Button(
-        onClick = { /* do something */ },
-        interactionSource = interactionSource) {
-        Text(if (isPressed) "Pressed!" else "Not pressed")
-    }
-}
-
-@Composable
-private fun InteractionSourceBuildList() {
-    val interactionSource = remember { MutableInteractionSource() }
-    val interactions = remember { mutableStateListOf<Interaction>() }
-
-    LaunchedEffect(interactionSource) {
-        interactionSource.interactions.collect { interaction ->
-            when (interaction) {
-                is PressInteraction.Press -> {
-                    interactions.add(interaction)
-                }
-                is DragInteraction.Start -> {
-                    interactions.add(interaction)
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun InteractionSourcePruneList() {
-    val interactionSource = remember { MutableInteractionSource() }
-
-    // snippet 1:
-
-    val interactions = remember { mutableStateListOf<Interaction>() }
-
-    LaunchedEffect(interactionSource) {
-        interactionSource.interactions.collect { interaction ->
-            when (interaction) {
-                is PressInteraction.Press -> {
-                    interactions.add(interaction)
-                }
-                is PressInteraction.Release -> {
-                    interactions.remove(interaction.press)
-                }
-                is PressInteraction.Cancel -> {
-                    interactions.remove(interaction.press)
-                }
-                is DragInteraction.Start -> {
-                    interactions.add(interaction)
-                }
-                is DragInteraction.Stop -> {
-                    interactions.remove(interaction.start)
-                }
-                is DragInteraction.Cancel -> {
-                    interactions.add(interaction.start)
-                }
-            }
-        }
-    }
-
-    // snippet 2:
-    val isPressedOrDragged = interactions.isNotEmpty()
-
-    // snippet 3:
-    val lastInteraction = when (interactions.lastOrNull()) {
-        is DragInteraction.Start -> "Dragged"
-        is PressInteraction.Press -> "Pressed"
-        else -> "No state"
-    }
-}
-
-private object DynamicButton {
-    @Composable
-    fun PressIconButton(
-        onClick: () -> Unit,
-        icon: @Composable () -> Unit,
-        text: @Composable () -> Unit,
-        modifier: Modifier = Modifier,
-        interactionSource: MutableInteractionSource =
-            remember { MutableInteractionSource() },
-    ) {
-        val isPressed by interactionSource.collectIsPressedAsState()
-        Button(onClick = onClick, modifier = modifier,
-            interactionSource = interactionSource) {
-            AnimatedVisibility(visible = isPressed) {
-                if (isPressed) {
-                    Row {
-                        icon()
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                    }
-                }
-            }
-            text()
-        }
-    }
-}
-
-@Composable
-private fun UseDynamicButton() {
-    PressIconButton(
-        onClick = {},
-        icon = { Icon(Icons.Filled.ShoppingCart, contentDescription = null) },
-        text = { Text("Add to cart") }
-    )
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-// none yet
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
deleted file mode 100644
index 3451066..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
-    "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
-    "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter"
-)
-
-package androidx.compose.integration.docs.interoperability
-
-import android.content.Context
-import android.os.Bundle
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.ComposeView
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop/compose-in-existing-arch
- *
- * No action required if it's modified.
- */
-
-private object InteropArchitectureSnippet1 {
-    class GreetingActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                MaterialTheme {
-                    Column {
-                        GreetingScreen("user1")
-                        GreetingScreen("user2")
-                    }
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun GreetingScreen(
-        userId: String,
-        viewModel: GreetingViewModel = viewModel(
-            factory = GreetingViewModelFactory(userId)
-        )
-    ) {
-        val messageUser by viewModel.message.observeAsState("")
-
-        Text(messageUser)
-    }
-
-    class GreetingViewModel(private val userId: String) : ViewModel() {
-        private val _message = MutableLiveData("Hi $userId")
-        val message: LiveData<String> = _message
-    }
-
-    class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
-        @Suppress("UNCHECKED_CAST")
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            return GreetingViewModel(userId) as T
-        }
-    }
-}
-
-private object InteropArchitectureSnippet2 {
-    @Composable
-    fun MyApp() {
-        NavHost(rememberNavController(), startDestination = "profile/{userId}") {
-            /* ... */
-            composable("profile/{userId}") { backStackEntry ->
-                GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "")
-            }
-        }
-    }
-
-    @Composable
-    fun GreetingScreen(
-        userId: String,
-        viewModel: GreetingViewModel = viewModel(
-            factory = GreetingViewModelFactory(userId)
-        )
-    ) {
-        val messageUser by viewModel.message.observeAsState("")
-
-        Text(messageUser)
-    }
-}
-
-private object InteropArchitectureSnippet3 {
-    @Composable
-    fun rememberAnalytics(user: User): FirebaseAnalytics {
-        val analytics: FirebaseAnalytics = remember {
-            // START - DO NOT COPY IN CODE SNIPPET
-            FirebaseAnalytics()
-            // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
-        }
-
-        // On every successful composition, update FirebaseAnalytics with
-        // the userType from the current User, ensuring that future analytics
-        // events have this metadata attached
-        SideEffect {
-            analytics.setUserProperty("userType", user.userType)
-        }
-        return analytics
-    }
-}
-
-private object InteropArchitectureSnippet4 {
-    class CustomViewGroup @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyle: Int = 0
-    ) : LinearLayout(context, attrs, defStyle) {
-
-        // Source of truth in the View system as mutableStateOf
-        // to make it thread-safe for Compose
-        private var text by mutableStateOf("")
-
-        private val textView: TextView
-
-        init {
-            orientation = VERTICAL
-
-            textView = TextView(context)
-            val composeView = ComposeView(context).apply {
-                setContent {
-                    MaterialTheme {
-                        TextField(value = text, onValueChange = { updateState(it) })
-                    }
-                }
-            }
-
-            addView(textView)
-            addView(composeView)
-        }
-
-        // Update both the source of truth and the TextView
-        private fun updateState(newValue: String) {
-            text = newValue
-            textView.text = newValue
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private class GreetingViewModel(userId: String) : ViewModel() {
-    val _message = MutableLiveData("")
-    val message: LiveData<String> = _message
-}
-private class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
-    @Suppress("UNCHECKED_CAST")
-    override fun <T : ViewModel> create(modelClass: Class<T>): T {
-        return GreetingViewModel(userId) as T
-    }
-}
-
-private class User(val userType: String = "user")
-private class FirebaseAnalytics {
-    fun setUserProperty(name: String, value: String) {}
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
deleted file mode 100644
index c4241f0..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
-    "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
-    "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter", "PackageDirectoryMismatch"
-)
-
-package androidx.compose.integration.docs.interoperabilityui
-
-import android.app.Activity
-import android.content.Context
-import android.os.Bundle
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.FloatingActionButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.AbstractComposeView
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.compose.ui.unit.dp
-import androidx.recyclerview.widget.RecyclerView
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui
- *
- * No action required if it's modified.
- */
-
-private object InteropUiSnippet1 {
-    @Composable
-    fun CallToActionButton(
-        text: String,
-        onClick: () -> Unit,
-        modifier: Modifier = Modifier,
-    ) {
-        Button(
-            colors = ButtonDefaults.buttonColors(
-                backgroundColor = MaterialTheme.colors.secondary
-            ),
-            onClick = onClick,
-            modifier = modifier,
-        ) {
-            Text(text)
-        }
-    }
-
-    class CallToActionViewButton @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyle: Int = 0
-    ) : AbstractComposeView(context, attrs, defStyle) {
-
-        var text by mutableStateOf<String>("")
-        var onClick by mutableStateOf<() -> Unit>({})
-
-        @Composable
-        override fun Content() {
-            YourAppTheme {
-                CallToActionButton(text, onClick)
-            }
-        }
-    }
-}
-
-private object InteropUiSnippet2 {
-    class ExampleActivity : Activity() {
-
-        private lateinit var binding: ActivityExampleBinding
-
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            binding = ActivityExampleBinding.inflate(layoutInflater)
-            setContentView(binding.root)
-
-            binding.callToAction.apply {
-                text = getString(R.string.something)
-                onClick = { /* Do something */ }
-            }
-        }
-    }
-}
-
-private object InteropUiSnippet3 {
-    // import com.google.android.material.composethemeadapter.MdcTheme
-
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                // Use MdcTheme instead of MaterialTheme
-                // Colors, typography, and shape have been read from the
-                // View-based theme used in this Activity
-                MdcTheme {
-                    ExampleComposable(/*...*/)
-                }
-            }
-        }
-    }
-}
-
-private object InteropUiSnippet4 {
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                AppCompatTheme {
-                    // Colors, typography, and shape have been read from the
-                    // View-based theme used in this Activity
-                    ExampleComposable(/*...*/)
-                }
-            }
-        }
-    }
-}
-
-private object InteropUiSnippet5 {
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            WindowCompat.setDecorFitsSystemWindows(window, false)
-
-            setContent {
-                MaterialTheme {
-                    MyScreen()
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun MyScreen() {
-        Box {
-            LazyColumn(
-                modifier = Modifier
-                    .fillMaxSize() // fill the entire window
-                    .imePadding() // padding for the bottom for the IME
-                    .imeNestedScroll(), // scroll IME at the bottom
-                content = { }
-            )
-            FloatingActionButton(
-                modifier = Modifier
-                    .align(Alignment.BottomEnd)
-                    .padding(16.dp) // normal 16dp of padding for FABs
-                    .navigationBarsPadding() // Move it out from under the nav bar
-                    .imePadding(), // padding for when IME appears
-                onClick = { }
-            ) {
-                Icon( /* ... */)
-            }
-        }
-    }
-}
-
-@Composable
-fun InteropUiSnippet6(showCautionIcon: Boolean) {
-    if (showCautionIcon) {
-        CautionIcon(/* ... */)
-    }
-}
-
-@Composable
-fun InteropUiSnippet7() {
-    var isEnabled by rememberSaveable { mutableStateOf(false) }
-
-    Column {
-        ImageWithEnabledOverlay(isEnabled)
-        ControlPanelWithToggle(
-            isEnabled = isEnabled,
-            onEnabledChanged = { isEnabled = it }
-        )
-    }
-}
-
-private object InteropUiSnippet8 {
-    @Composable
-    fun MyComposable() {
-        BoxWithConstraints {
-            if (minWidth < 480.dp) {
-                /* Show grid with 4 columns */
-            } else if (minWidth < 720.dp) {
-                /* Show grid with 8 columns */
-            } else {
-                /* Show grid with 12 columns */
-            }
-        }
-    }
-}
-
-private object InteropUiSnippet9 {
-    // import androidx.compose.ui.platform.ComposeView
-
-    class MyComposeAdapter : RecyclerView.Adapter<MyComposeViewHolder>() {
-
-        override fun onCreateViewHolder(
-            parent: ViewGroup,
-            viewType: Int,
-        ): MyComposeViewHolder {
-            return MyComposeViewHolder(ComposeView(parent.context))
-        }
-
-        override fun onViewRecycled(holder: MyComposeViewHolder) {
-            // Dispose the underlying Composition of the ComposeView
-            // when RecyclerView has recycled this ViewHolder
-            holder.composeView.disposeComposition()
-        }
-
-        /* Other methods */
-
-        // NOTE: DO NOT COPY THE METHODS BELOW IN THE CODE SNIPPETS
-        override fun onBindViewHolder(holder: MyComposeViewHolder, position: Int) {
-            TODO("Not yet implemented")
-        }
-
-        override fun getItemCount(): Int {
-            TODO("Not yet implemented")
-        }
-    }
-
-    class MyComposeViewHolder(
-        val composeView: ComposeView
-    ) : RecyclerView.ViewHolder(composeView) {
-        /* ... */
-    }
-}
-
-private object InteropUiSnippet10 {
-    // import androidx.compose.ui.platform.ViewCompositionStrategy
-
-    class MyComposeViewHolder(
-        val composeView: ComposeView
-    ) : RecyclerView.ViewHolder(composeView) {
-
-        init {
-            composeView.setViewCompositionStrategy(
-                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
-            )
-        }
-
-        fun bind(input: String) {
-            composeView.setContent {
-                MdcTheme {
-                    Text(input)
-                }
-            }
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private object R {
-    object string {
-        const val something = 1
-    }
-}
-
-private fun ExampleComposable() {}
-@Composable
-private fun MdcTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun AppCompatTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun BlueTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun PinkTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun YourAppTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun Icon() {
-}
-
-@Composable
-private fun CautionIcon() {
-}
-
-@Composable
-private fun ImageWithEnabledOverlay(isEnabled: Boolean) {
-}
-
-@Composable
-private fun ControlPanelWithToggle(
-    isEnabled: Boolean,
-    onEnabledChanged: (Boolean) -> Unit
-) {
-}
-
-private class WindowCompat {
-    companion object {
-        fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
-    }
-}
-
-private fun Modifier.navigationBarsPadding(): Modifier = this
-
-private fun Modifier.fillMaxSize(): Modifier = this
-
-private fun Modifier.imePadding(): Modifier = this
-
-private fun Modifier.imeNestedScroll(): Modifier = this
-
-private class ActivityExampleBinding {
-    val root: Int = 0
-    lateinit var callToAction: InteropUiSnippet1.CallToActionViewButton
-    companion object {
-        fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
deleted file mode 100644
index 369ff5f..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ /dev/null
@@ -1,384 +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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
-    "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
-    "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter"
-)
-
-package androidx.compose.integration.docs.interoperability
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.integration.docs.databinding.ExampleLayoutBinding
-import androidx.compose.material.Button
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.compose.ui.viewinterop.AndroidViewBinding
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop/interop-apis
- *
- * No action required if it's modified.
- */
-
-private object InteropSnippet1 {
-    class ExampleActivity : ComponentActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent { // In here, we can call composables!
-                MaterialTheme {
-                    Greeting(name = "compose")
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun Greeting(name: String) {
-        Text(text = "Hello $name!")
-    }
-}
-
-private object InteropSnippet2 {
-    class ExampleFragment : Fragment() {
-
-        private var _binding: FragmentExampleBinding? = null
-        // This property is only valid between onCreateView and onDestroyView.
-        private val binding get() = _binding!!
-
-        override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
-        ): View {
-            _binding = FragmentExampleBinding.inflate(inflater, container, false)
-            val view = binding.root
-            binding.composeView.apply {
-                // Dispose of the Composition when the view's LifecycleOwner
-                // is destroyed
-                setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
-                setContent {
-                    // In Compose world
-                    MaterialTheme {
-                        Text("Hello Compose!")
-                    }
-                }
-            }
-            return view
-        }
-
-        override fun onDestroyView() {
-            super.onDestroyView()
-            _binding = null
-        }
-    }
-}
-
-private object InteropSnippet3 {
-    class ExampleFragment : Fragment() {
-
-        override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
-        ): View {
-            return ComposeView(requireContext()).apply {
-                // Dispose of the Composition when the view's LifecycleOwner
-                // is destroyed
-                setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
-                setContent {
-                    MaterialTheme {
-                        // In Compose world
-                        Text("Hello Compose!")
-                    }
-                }
-            }
-        }
-    }
-}
-
-/* ktlint-disable indent */
-private object InteropSnippet4 {
-    // TW: Do not use this snippet.
-    // This snippet simplifies too much code from Fragment and View so check out the following
-    // snippet for changes:
-
-    class ExampleFragment : Fragment() {
-        override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
-        ): View {
-            return LinearLayout(context).apply {
-                addView(ComposeView(context).apply {
-                    id = R.id.compose_view_x
-                })
-            }
-        }
-    }
-}
-/* ktlint-enable indent */
-
-private object InteropSnippet5 {
-    @Composable
-    fun CustomView() {
-        val selectedItem = remember { mutableIntStateOf(0) }
-
-        // Adds view to Compose
-        AndroidView(
-            modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
-            factory = { context ->
-                // Creates custom view
-                CustomView(context).apply {
-                    // Sets up listeners for View -> Compose communication
-                    myView.setOnClickListener {
-                        selectedItem.intValue = 1
-                    }
-                }
-            },
-            update = { view ->
-                // View's been inflated or state read in this block has been updated
-                // Add logic here if necessary
-
-                // As selectedItem is read here, AndroidView will recompose
-                // whenever the state changes
-                // Example of Compose -> View communication
-                view.coordinator.selectedItem = selectedItem.intValue
-            }
-        )
-    }
-
-    @Composable
-    fun ContentExample() {
-        Column(Modifier.fillMaxSize()) {
-            Text("Look at this CustomView!")
-            CustomView()
-        }
-    }
-}
-
-private object InteropSnippet6 {
-    @Composable
-    fun AndroidViewBindingExample() {
-        AndroidViewBinding(ExampleLayoutBinding::inflate) {
-            exampleView.setBackgroundColor(Color.GRAY)
-        }
-    }
-}
-
-private object InteropSnippet7 {
-    @Composable
-    fun ToastGreetingButton(greeting: String) {
-        val context = LocalContext.current
-        Button(onClick = {
-            Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
-        }) {
-            Text("Greet")
-        }
-    }
-}
-
-/* ktlint-disable indent */
-private object InteropSnippet8 {
-    class ExampleActivity : ComponentActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            // get data from savedInstanceState
-            setContent {
-                MaterialTheme {
-                    ExampleComposable(data, onButtonClick = {
-                        startActivity(/*...*/)
-                    })
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
-        Button(onClick = onButtonClick) {
-            Text(data.title)
-        }
-    }
-}
-
-private object InteropSnippet9 {
-    @Composable
-    fun SystemBroadcastReceiver(
-        systemAction: String,
-        onSystemEvent: (intent: Intent?) -> Unit
-    ) {
-        // Grab the current context in this part of the UI tree
-        val context = LocalContext.current
-
-        // Safely use the latest onSystemEvent lambda passed to the function
-        val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)
-
-        // If either context or systemAction changes, unregister and register again
-        DisposableEffect(context, systemAction) {
-            val intentFilter = IntentFilter(systemAction)
-            val broadcast = object : BroadcastReceiver() {
-                override fun onReceive(context: Context?, intent: Intent?) {
-                    currentOnSystemEvent(intent)
-                }
-            }
-
-            context.registerReceiver(broadcast, intentFilter)
-
-            // When the effect leaves the Composition, remove the callback
-            onDispose {
-                context.unregisterReceiver(broadcast)
-            }
-        }
-    }
-
-    @Composable
-    fun HomeScreen() {
-
-        SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
-            val isCharging = /* Get from batteryStatus ... */ true
-            /* Do something if the device is charging */
-        }
-
-        /* Rest of the HomeScreen */
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private object R {
-    object layout {
-        const val fragment_example = 1
-    }
-
-    object id {
-        const val compose_view = 2
-        const val compose_view_x = 3
-    }
-
-    object string {
-        const val ok = 4
-        const val plane_description = 5
-    }
-
-    object dimen {
-        const val padding_small = 6
-    }
-
-    object drawable {
-        const val ic_plane = 7
-    }
-
-    object color {
-        const val Blue700 = 8
-    }
-}
-
-private class CustomView(context: Context) : View(context) {
-    class Coord(var selectedItem: Int = 0)
-
-    val coordinator = Coord()
-    lateinit var myView: View
-}
-
-private class DataExample(val title: String = "")
-
-private val data = DataExample()
-private fun startActivity(): Nothing = TODO()
-private class ExampleViewModel : ViewModel() {
-    val exampleLiveData = MutableLiveData(" ")
-}
-
-private fun ShowData(dataExample: State<String?>): Nothing = TODO()
-private class ExampleImageLoader {
-    fun load(url: String): DummyInto = TODO()
-    fun cancel(listener: Listener): Any = TODO()
-
-    open class Listener {
-        open fun onSuccess(bitmap: Bitmap): Unit = TODO()
-    }
-
-    companion object {
-        fun get() = ExampleImageLoader()
-    }
-}
-
-private class DummyInto {
-    fun into(listener: ExampleImageLoader.Listener) {}
-}
-
-private open class Fragment {
-
-    lateinit var context: Context
-    open fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View {
-        TODO("not implemented")
-    }
-
-    fun requireContext(): Context = TODO()
-
-    open fun onDestroyView() { }
-}
-
-private class FragmentExampleBinding {
-    val root: View = TODO()
-    var composeView: ComposeView
-    companion object {
-        fun inflate(
-            li: LayoutInflater,
-            container: ViewGroup?,
-            boolean: Boolean
-        ): FragmentExampleBinding { TODO() }
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/LayoutBasics.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/LayoutBasics.kt
deleted file mode 100644
index 5e3c966..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/LayoutBasics.kt
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "RemoveEmptyParenthesesFromLambdaCall"
-)
-
-package androidx.compose.integration.docs.layout
-
-import android.annotation.SuppressLint
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.material.Card
-import androidx.compose.material.Scaffold
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/layouts/basics
- *
- * No action required if it's modified.
- */
-
-private object LayoutBasicsSnippet1 {
-    @Composable
-    fun ArtistCard() {
-        Text("Alfred Sisley")
-        Text("3 minutes ago")
-    }
-}
-
-private object LayoutBasicsSnippet2 {
-    @Composable
-    fun ArtistCard() {
-        Column {
-            Text("Alfred Sisley")
-            Text("3 minutes ago")
-        }
-    }
-}
-
-private object LayoutBasicsSnippet3 {
-    @Composable
-    fun ArtistCard(artist: Artist) {
-        Row(verticalAlignment = Alignment.CenterVertically) {
-            Image(/*...*/)
-            Column {
-                Text(artist.name)
-                Text(artist.lastSeenOnline)
-            }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet4 {
-    @Composable
-    fun ArtistCard(artist: Artist) {
-        Row(
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = Arrangement.End
-        ) {
-            Image(/*...*/)
-            Column { /*...*/ }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet5 {
-    @Composable
-    fun ArtistCard(
-        artist: Artist,
-        onClick: () -> Unit
-    ) {
-        val padding = 16.dp
-        Column(
-            Modifier
-                .clickable(onClick = onClick)
-                .padding(padding)
-                .fillMaxWidth()
-        ) {
-            Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
-            Spacer(Modifier.size(padding))
-            Card(elevation = 4.dp) { /*...*/ }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet6 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        val padding = 16.dp
-        Column(
-            Modifier
-                .clickable(onClick = onClick)
-                .padding(padding)
-                .fillMaxWidth()
-        ) {
-            // rest of the implementation
-        }
-    }
-}
-
-private object LayoutBasicsSnippet7 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        val padding = 16.dp
-        Column(
-            Modifier
-                .padding(padding)
-                .clickable(onClick = onClick)
-                .fillMaxWidth()
-        ) {
-            // rest of the implementation
-        }
-    }
-}
-
-private object LayoutBasicsSnippet8 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        Row(
-            modifier = Modifier.size(width = 400.dp, height = 100.dp)
-        ) {
-            Image(/*...*/)
-            Column { /*...*/ }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet9 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        Row(
-            modifier = Modifier.size(width = 400.dp, height = 100.dp)
-        ) {
-            Image(
-                /*...*/
-                modifier = Modifier.requiredSize(150.dp)
-            )
-            Column { /*...*/ }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet10 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        Row(
-            modifier = Modifier.size(width = 400.dp, height = 100.dp)
-        ) {
-            Image(
-                /*...*/
-                modifier = Modifier.fillMaxHeight()
-            )
-            Column { /*...*/ }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet11 {
-    @Composable
-    fun MatchParentSizeComposable() {
-        Box {
-            Spacer(Modifier.matchParentSize().background(Color.LightGray))
-            ArtistCard()
-        }
-    }
-}
-
-private object LayoutBasicsSnippet12 {
-    @Composable
-    fun ArtistCard(artist: Artist) {
-        Row(/*...*/) {
-            Column {
-                Text(
-                    text = artist.name,
-                    modifier = Modifier.paddingFromBaseline(top = 50.dp)
-                )
-                Text(artist.lastSeenOnline)
-            }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet13 {
-    @Composable
-    fun ArtistCard(artist: Artist) {
-        Row(/*...*/) {
-            Column {
-                Text(artist.name)
-                Text(
-                    text = artist.lastSeenOnline,
-                    modifier = Modifier.offset(x = 4.dp)
-                )
-            }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet14 {
-    @Composable
-    fun ArtistCard(/*...*/) {
-        Row(
-            modifier = Modifier.fillMaxWidth()
-        ) {
-            Image(
-                /*...*/
-                modifier = Modifier.weight(2f)
-            )
-            Column(
-                modifier = Modifier.weight(1f)
-            ) {
-                /*...*/
-            }
-        }
-    }
-}
-
-private object LayoutBasicsSnippet15 {
-    @Composable
-    fun WithConstraintsComposable() {
-        BoxWithConstraints {
-            Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object LayoutBasicsSnippet16 {
-    @Composable
-    fun HomeScreen(/*...*/) {
-        Scaffold(
-            drawerContent = { /*...*/ },
-            topBar = { /*...*/ },
-            content = { /*...*/ }
-        )
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private data class Artist(val name: String, val lastSeenOnline: String)
-private class Image
-
-@Composable
-private fun Image(modifier: Modifier = Modifier) {
-}
-
-@Composable
-private fun ArtistCard(modifier: Modifier = Modifier) {
-}
-
-private val onClick = {}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Material.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Material.kt
deleted file mode 100644
index 8756730..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Material.kt
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "RemoveEmptyParenthesesFromLambdaCall"
-)
-
-package androidx.compose.integration.docs.layout
-
-import android.annotation.SuppressLint
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.material.BackdropScaffold
-import androidx.compose.material.BackdropValue
-import androidx.compose.material.BottomAppBar
-import androidx.compose.material.BottomDrawer
-import androidx.compose.material.BottomDrawerValue
-import androidx.compose.material.BottomSheetScaffold
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.DrawerValue
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.ExtendedFloatingActionButton
-import androidx.compose.material.FabPosition
-import androidx.compose.material.FloatingActionButton
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.ModalDrawer
-import androidx.compose.material.Scaffold
-import androidx.compose.material.SnackbarDuration
-import androidx.compose.material.SnackbarResult
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material.rememberBackdropScaffoldState
-import androidx.compose.material.rememberBottomDrawerState
-import androidx.compose.material.rememberBottomSheetScaffoldState
-import androidx.compose.material.rememberDrawerState
-import androidx.compose.material.rememberModalBottomSheetState
-import androidx.compose.material.rememberScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.launch
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/layouts/material
- *
- * No action required if it's modified.
- */
-
-private object MaterialSnippet1 {
-    @Composable
-    fun MyApp() {
-        MaterialTheme {
-            // Material Components like Button, Card, Switch, etc.
-        }
-    }
-}
-
-private object MaterialSnippet2 {
-    @Composable
-    fun MyButton() {
-        Button(
-            onClick = { /* ... */ },
-            // Uses ButtonDefaults.ContentPadding by default
-            contentPadding = PaddingValues(
-                start = 20.dp,
-                top = 12.dp,
-                end = 20.dp,
-                bottom = 12.dp
-            )
-        ) {
-            // Inner content including an icon and a text label
-            Icon(
-                Icons.Filled.Favorite,
-                contentDescription = "Favorite",
-                modifier = Modifier.size(ButtonDefaults.IconSize)
-            )
-            Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-            Text("Like")
-        }
-    }
-}
-
-private object MaterialSnippet3 {
-    @Composable
-    fun MyExtendedFloatingActionButton() {
-        ExtendedFloatingActionButton(
-            onClick = { /* ... */ },
-            icon = {
-                Icon(
-                    Icons.Filled.Favorite,
-                    contentDescription = "Favorite"
-                )
-            },
-            text = { Text("Like") }
-        )
-    }
-}
-
-private object MaterialSnippet4 {
-    @Composable
-    fun MyScaffold() {
-        Scaffold(/* ... */) { contentPadding ->
-            // Screen content
-            Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet5 {
-    @Composable
-    fun MyTopAppBar() {
-        Scaffold(
-            topBar = {
-                TopAppBar { /* Top app bar content */ }
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet6 {
-    @Composable
-    fun MyBottomAppBar() {
-        Scaffold(
-            bottomBar = {
-                BottomAppBar { /* Bottom app bar content */ }
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet7 {
-    @Composable
-    fun MyFAB() {
-        Scaffold(
-            floatingActionButton = {
-                FloatingActionButton(onClick = { /* ... */ }) {
-                    /* FAB content */
-                }
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet8 {
-    @Composable
-    fun MyFAB() {
-        Scaffold(
-            floatingActionButton = {
-                FloatingActionButton(onClick = { /* ... */ }) {
-                    /* FAB content */
-                }
-            },
-            // Defaults to FabPosition.End
-            floatingActionButtonPosition = FabPosition.Center
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet9 {
-    @Composable
-    fun MyFAB() {
-        Scaffold(
-            floatingActionButton = {
-                FloatingActionButton(onClick = { /* ... */ }) {
-                    /* FAB content */
-                }
-            },
-            // Defaults to false
-            isFloatingActionButtonDocked = true,
-            bottomBar = {
-                BottomAppBar { /* Bottom app bar content */ }
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet10 {
-    @Composable
-    fun MyFAB() {
-        Scaffold(
-            floatingActionButton = {
-                FloatingActionButton(onClick = { /* ... */ }) {
-                    /* FAB content */
-                }
-            },
-            isFloatingActionButtonDocked = true,
-            bottomBar = {
-                BottomAppBar(
-                    // Defaults to null, that is, no cutout
-                    cutoutShape = MaterialTheme.shapes.small.copy(
-                        CornerSize(percent = 50)
-                    )
-                ) {
-                    /* Bottom app bar content */
-                }
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet11 {
-    @Composable
-    fun MySnackbar() {
-        val scaffoldState = rememberScaffoldState()
-        val scope = rememberCoroutineScope()
-        Scaffold(
-            scaffoldState = scaffoldState,
-            floatingActionButton = {
-                ExtendedFloatingActionButton(
-                    text = { Text("Show snackbar") },
-                    onClick = {
-                        scope.launch {
-                            scaffoldState.snackbarHostState
-                                .showSnackbar("Snackbar")
-                        }
-                    }
-                )
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet12 {
-    @Composable
-    fun MySnackbar() {
-        val scaffoldState = rememberScaffoldState()
-        val scope = rememberCoroutineScope()
-        Scaffold(
-            scaffoldState = scaffoldState,
-            floatingActionButton = {
-                ExtendedFloatingActionButton(
-                    text = { Text("Show snackbar") },
-                    onClick = {
-                        scope.launch {
-                            val result = scaffoldState.snackbarHostState
-                                .showSnackbar(
-                                    message = "Snackbar",
-                                    actionLabel = "Action",
-                                    // Defaults to SnackbarDuration.Short
-                                    duration = SnackbarDuration.Indefinite
-                                )
-                            when (result) {
-                                SnackbarResult.ActionPerformed -> {
-                                    /* Handle snackbar action performed */
-                                }
-                                SnackbarResult.Dismissed -> {
-                                    /* Handle snackbar dismissed */
-                                }
-                            }
-                        }
-                    }
-                )
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet13 {
-    @Composable
-    fun MyDrawer() {
-        Scaffold(
-            drawerContent = {
-                Text("Drawer title", modifier = Modifier.padding(16.dp))
-                Divider()
-                // Drawer items
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet14 {
-    @Composable
-    fun MyDrawer() {
-        Scaffold(
-            drawerContent = {
-                // Drawer content
-            },
-            // Defaults to true
-            drawerGesturesEnabled = false
-        ) {
-            // Screen content
-        }
-    }
-}
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-private object MaterialSnippet15 {
-    @Composable
-    fun MyDrawer() {
-        val scaffoldState = rememberScaffoldState()
-        val scope = rememberCoroutineScope()
-        Scaffold(
-            scaffoldState = scaffoldState,
-            drawerContent = {
-                // Drawer content
-            },
-            floatingActionButton = {
-                ExtendedFloatingActionButton(
-                    text = { Text("Open or close drawer") },
-                    onClick = {
-                        scope.launch {
-                            scaffoldState.drawerState.apply {
-                                if (isClosed) open() else close()
-                            }
-                        }
-                    }
-                )
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet16 {
-    @Composable
-    fun MyModalDrawer() {
-        val drawerState = rememberDrawerState(DrawerValue.Closed)
-        ModalDrawer(
-            drawerState = drawerState,
-            drawerContent = {
-                // Drawer content
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet17 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyModalDrawer() {
-        val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
-        BottomDrawer(
-            drawerState = drawerState,
-            drawerContent = {
-                // Drawer content
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet18 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBottomSheet() {
-        BottomSheetScaffold(
-            sheetContent = {
-                // Sheet content
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet19 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBottomSheet() {
-        BottomSheetScaffold(
-            sheetContent = {
-                // Sheet content
-            },
-            // Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
-            sheetPeekHeight = 128.dp,
-            // Defaults to true
-            sheetGesturesEnabled = false
-
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet20 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBottomSheet() {
-        val scaffoldState = rememberBottomSheetScaffoldState()
-        val scope = rememberCoroutineScope()
-        BottomSheetScaffold(
-            scaffoldState = scaffoldState,
-            sheetContent = {
-                // Sheet content
-            },
-            floatingActionButton = {
-                ExtendedFloatingActionButton(
-                    text = { Text("Expand or collapse sheet") },
-                    onClick = {
-                        scope.launch {
-                            scaffoldState.bottomSheetState.apply {
-                                if (isCollapsed) expand() else collapse()
-                            }
-                        }
-                    }
-                )
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet21 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBottomSheet() {
-        val sheetState = rememberModalBottomSheetState(
-            ModalBottomSheetValue.Hidden
-        )
-        ModalBottomSheetLayout(
-            sheetState = sheetState,
-            sheetContent = {
-                // Sheet content
-            }
-        ) {
-            // Screen content
-        }
-    }
-}
-
-private object MaterialSnippet22 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBackdrop() {
-        BackdropScaffold(
-            appBar = {
-                // Top app bar
-            },
-            backLayerContent = {
-                // Back layer content
-            },
-            frontLayerContent = {
-                // Front layer content
-            }
-        )
-    }
-}
-
-private object MaterialSnippet23 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBackdrop() {
-        BackdropScaffold(
-            appBar = {
-                // Top app bar
-            },
-            backLayerContent = {
-                // Back layer content
-            },
-            frontLayerContent = {
-                // Front layer content
-            },
-            // Defaults to BackdropScaffoldDefaults.PeekHeight
-            peekHeight = 40.dp,
-            // Defaults to BackdropScaffoldDefaults.HeaderHeight
-            headerHeight = 60.dp,
-            // Defaults to true
-            gesturesEnabled = false
-        )
-    }
-}
-
-private object MaterialSnippet24 {
-    @ExperimentalMaterialApi
-    @Composable
-    fun MyBackdrop() {
-        val scaffoldState = rememberBackdropScaffoldState(
-            BackdropValue.Concealed
-        )
-        val scope = rememberCoroutineScope()
-        BackdropScaffold(
-            scaffoldState = scaffoldState,
-            appBar = {
-                TopAppBar(
-                    title = { Text("Backdrop") },
-                    navigationIcon = {
-                        if (scaffoldState.isConcealed) {
-                            IconButton(
-                                onClick = {
-                                    scope.launch { scaffoldState.reveal() }
-                                }
-                            ) {
-                                Icon(
-                                    Icons.Default.Menu,
-                                    contentDescription = "Menu"
-                                )
-                            }
-                        } else {
-                            IconButton(
-                                onClick = {
-                                    scope.launch { scaffoldState.conceal() }
-                                }
-                            ) {
-                                Icon(
-                                    Icons.Default.Close,
-                                    contentDescription = "Close"
-                                )
-                            }
-                        }
-                    },
-                    elevation = 0.dp,
-                    backgroundColor = Color.Transparent
-                )
-            },
-            backLayerContent = {
-                // Back layer content
-            },
-            frontLayerContent = {
-                // Front layer content
-            }
-        )
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
deleted file mode 100644
index 1418881..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
-    "RedundantSuspendModifier", "CascadeIf", "ClassName", "SameParameterValue"
-)
-
-package androidx.compose.integration.docs.libraries
-
-import android.graphics.Bitmap
-import android.net.Uri
-import androidx.activity.compose.BackHandler
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts.GetContent
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material.Button
-import androidx.compose.material.CircularProgressIndicator
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
-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.graphics.painter.Painter
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.IntSize
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.navigation
-import kotlinx.coroutines.flow.Flow
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/libraries
- *
- * No action required if it's modified.
- */
-
-private object LibrariesSnippetActivityResult {
-    @Composable
-    fun GetContentExample() {
-        var imageUri by remember { mutableStateOf<Uri?>(null) }
-        val launcher = rememberLauncherForActivityResult(GetContent()) { uri: Uri? ->
-            imageUri = uri
-        }
-        Column {
-            Button(onClick = { launcher.launch("image/*") }) {
-                Text(text = "Load Image")
-            }
-            Image(
-                painter = rememberImagePainter(imageUri),
-                contentDescription = "My Image"
-            )
-        }
-    }
-}
-
-@Composable
-private fun LibrariesSnippetBackHandler() {
-    var backHandlingEnabled by remember { mutableStateOf(true) }
-    BackHandler(backHandlingEnabled) {
-        // Handle back press
-    }
-}
-
-private object LibrariesSnippetAddingViewModel {
-    class MyViewModel : ViewModel() { /*...*/ }
-
-    @Composable
-    fun MyScreen(
-        viewModel: MyViewModel = viewModel()
-    ) {
-        // use viewModel here
-    }
-}
-
-private object LibrariesSnippetSameViewModelTwice {
-    @Composable
-    fun MyScreen(
-        // Returns the same instance as long as the activity is alive,
-        // just as if you grabbed the instance from an Activity or Fragment
-        viewModel: MyViewModel = viewModel()
-    ) { /* ... */ }
-
-    @Composable
-    fun MyScreen2(
-        viewModel: MyViewModel = viewModel() // Same instance as in MyExample
-    ) { /* ... */ }
-}
-
-private object LibrariesSnippetRecomposesWhenStateChanges {
-    @Composable
-    fun MyScreen(
-        viewModel: MyViewModel = viewModel()
-    ) {
-        val dataExample = viewModel.exampleLiveData.observeAsState()
-
-        // Because the state is read here,
-        // MyExample recomposes whenever dataExample changes.
-        dataExample.value?.let {
-            ShowData(dataExample)
-        }
-    }
-}
-
-private object LibrariesSnippetHilt {
-    @HiltViewModel
-    class MyViewModel @Inject constructor(
-        private val savedStateHandle: SavedStateHandle,
-        private val repository: ExampleRepository
-    ) : ViewModel() { /* ... */ }
-
-    @Composable
-    fun MyScreen(
-        viewModel: MyViewModel = viewModel()
-    ) { /* ... */ }
-}
-
-private object LibrariesSnippetHiltViewModel {
-    // import androidx.hilt.navigation.compose.hiltViewModel
-
-    @Composable
-    fun MyApp() {
-        NavHost(navController, startDestination = startRoute) {
-            composable("example") { backStackEntry ->
-                // Creates a ViewModel from the current BackStackEntry
-                // Available in the androidx.hilt:hilt-navigation-compose artifact
-                val viewModel = hiltViewModel<MyViewModel>()
-                MyScreen(viewModel)
-            }
-            /* ... */
-        }
-    }
-}
-
-private object LibrariesSnippetBackStackEntry {
-    // import androidx.hilt.navigation.compose.hiltViewModel
-    // import androidx.navigation.compose.getBackStackEntry
-
-    @Composable
-    fun MyApp() {
-        NavHost(navController, startDestination = startRoute) {
-            navigation(startDestination = innerStartRoute, route = "Parent") {
-                // ...
-                composable("exampleWithRoute") { backStackEntry ->
-                    val parentEntry = remember(backStackEntry) {
-                        navController.getBackStackEntry("Parent")
-                    }
-                    val parentViewModel = hiltViewModel<ParentViewModel>(
-                        parentEntry
-                    )
-                    ExampleWithRouteScreen(parentViewModel)
-                }
-            }
-        }
-    }
-}
-
-private object LibrariesSnippetPaging {
-    @Composable
-    fun MyScreen(flow: Flow<PagingData<String>>) {
-        val lazyPagingItems = flow.collectAsLazyPagingItems()
-        LazyColumn {
-            items(lazyPagingItems) {
-                Text("Item is $it")
-            }
-        }
-    }
-}
-
-private object LibrariesSnippetRemoteImages {
-    @Composable
-    fun MyScreen() {
-        val painter = rememberImagePainter(
-            data = "https://picsum.photos/300/300",
-            builder = {
-                crossfade(true)
-            }
-        )
-
-        Box {
-            Image(
-                painter = painter,
-                contentDescription = stringResource(R.string.image_content_desc),
-            )
-
-            when (painter.state) {
-                is ImagePainter.State.Loading -> {
-                    // Display a circular progress indicator whilst loading
-                    CircularProgressIndicator(Modifier.align(Alignment.Center))
-                }
-                is ImagePainter.State.Error -> {
-                    // If you wish to display some content if the request fails
-                }
-            }
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private object R {
-    object drawable {
-        const val ic_error = 1
-    }
-    object string {
-        const val image_content_desc = 2
-    }
-}
-
-private fun ShowData(dataExample: State<String?>): Nothing = TODO()
-private class ExampleImageLoader {
-    fun load(url: String): DummyInto = TODO()
-    fun cancel(listener: Listener): Any = TODO()
-
-    open class Listener {
-        open fun onSuccess(bitmap: Bitmap): Unit = TODO()
-    }
-
-    companion object {
-        fun get() = ExampleImageLoader()
-    }
-}
-
-private class DummyInto {
-    fun into(listener: ExampleImageLoader.Listener) {}
-}
-
-private class SavedStateHandle
-private class ExampleRepository
-private annotation class HiltViewModel
-private annotation class Inject
-
-private class ParentViewModel : ViewModel()
-private class MyViewModel : ViewModel() {
-    val exampleLiveData = MutableLiveData(" ")
-}
-
-private inline fun <reified VM : ViewModel> hiltViewModel(): VM { TODO() }
-private inline fun <reified VM : ViewModel> hiltViewModel(backStackEntry: NavBackStackEntry): VM {
-    TODO()
-}
-
-@Composable
-private fun MyScreen(vm: MyViewModel) {
-    TODO()
-}
-
-@Composable
-private fun ExampleWithRouteScreen(vm: ParentViewModel) {
-    TODO()
-}
-
-private val navController: NavHostController = TODO()
-private val innerStartRoute: String = TODO()
-private val startRoute: String = TODO()
-
-private class PagingData<T>
-
-private fun Flow<PagingData<String>>.collectAsLazyPagingItems() = listOf("")
-
-// Coil
-interface ImageRequest { interface Builder }
-
-@Composable
-fun rememberImagePainter(
-    data: Any?,
-    builder: ImageRequest.Builder.() -> Unit = {},
-): LoadPainter { TODO() }
-fun ImageRequest.Builder.crossfade(enable: Boolean): Nothing = TODO()
-
-fun interface Loader<R> {
-    fun load(request: R, size: IntSize): Flow<ImagePainter.State>
-}
-abstract class LoadPainter : Painter() {
-    var state: ImagePainter.State by mutableStateOf(ImagePainter.State.Loading)
-        private set
-}
-interface ImagePainter {
-    sealed class State {
-        object Loading : State()
-        object Error : State()
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt
deleted file mode 100644
index f85e23d..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "SimplifyBooleanWithConstants"
-)
-
-package androidx.compose.integration.docs.lifecycle
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.key
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/lifecycle
- *
- * No action required if it's modified.
- */
-
-private object LifecycleSnippet1 {
-    @Composable
-    fun MyComposable() {
-        Column {
-            Text("Hello")
-            Text("World")
-        }
-    }
-}
-private object LifecycleSnippet2 {
-    @Composable
-    fun LoginScreen(showError: Boolean) {
-        if (showError) {
-            LoginError()
-        }
-        LoginInput() // This call site affects where LoginInput is placed in Composition
-    }
-
-    @Composable
-    fun LoginInput() { /* ... */ }
-}
-
-private object LifecycleSnippet3 {
-    @Composable
-    fun MoviesScreen(movies: List<Movie>) {
-        Column {
-            for (movie in movies) {
-                // MovieOverview composables are placed in Composition given its
-                // index position in the for loop
-                MovieOverview(movie)
-            }
-        }
-    }
-}
-
-private object LifecycleSnippet4 {
-    @Composable
-    fun MovieOverview(movie: Movie) {
-        Column {
-            // Side effect explained later in the docs. If MovieOverview
-            // recomposes, while fetching the image is in progress,
-            // it is cancelled and restarted.
-            val image = loadNetworkImage(movie.url)
-            MovieHeader(image)
-
-            /* ... */
-        }
-    }
-}
-
-private object LifecycleSnippet5 {
-    @Composable
-    fun MoviesScreen(movies: List<Movie>) {
-        Column {
-            for (movie in movies) {
-                key(movie.id) { // Unique ID for this movie
-                    MovieOverview(movie)
-                }
-            }
-        }
-    }
-}
-
-private object LifecycleSnippet6 {
-    @Composable
-    fun MoviesScreen(movies: List<Movie>) {
-        LazyColumn {
-            items(movies, key = { movie -> movie.id }) { movie ->
-                MovieOverview(movie)
-            }
-        }
-    }
-}
-
-private object LifecycleSnippet7 {
-    // Marking the type as stable to favor skipping and smart recompositions.
-    @Stable
-    interface UiState<T : Result<T>> {
-        val value: T?
-        val exception: Throwable?
-
-        val hasError: Boolean
-            get() = exception != null
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-@Composable
-private fun LoginError() { }
-
-@Composable
-private fun MovieOverview(movie: Movie) { }
-@Composable
-private fun MovieHeader(movie: String) { }
-private data class Movie(val id: Long, val url: String = "")
-
-private fun loadNetworkImage(url: String): String = ""
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt
deleted file mode 100644
index 3ad5fb2..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt
+++ /dev/null
@@ -1,105 +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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
-
-package androidx.compose.integration.docs.preview
-
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults.buttonColors
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.tooling.preview.Preview
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/preview
- *
- * No action required if it's modified.
- */
-
-private object PreviewSnippet1 {
-    @Composable
-    fun Greeting(name: String) {
-        Text(text = "Hello $name!")
-    }
-}
-
-private object PreviewSnippet2 {
-    @Preview
-    @Composable
-    fun PreviewGreeting() {
-        Greeting("Android")
-    }
-}
-
-private object PreviewSnippet3 {
-    @Preview(name = "Android greeting")
-    @Composable
-    fun PreviewGreeting() {
-        Greeting("Android")
-    }
-}
-
-private object PreviewSnippet4 {
-    @Preview(name = "Long greeting")
-    @Composable
-    fun PreviewLongGreeting() {
-        Greeting("my valued friend, whom I am incapable of " +
-            "greeting without using a great many words")
-    }
-    @Preview(name = "Newline greeting")
-    @Composable
-    fun PreviewNewlineGreeting() {
-        Greeting("world\nwith a line break")
-    }
-}
-
-private object PreviewSnippet5 {
-    @Composable
-    fun Counter(count: Int, updateCount: (Int) -> Unit) {
-        Button(
-            onClick = { updateCount(count + 1) },
-            colors = buttonColors(
-                backgroundColor = if (count > 5) Color.Green else Color.White
-            )
-        ) {
-            Text("I've been clicked $count times")
-        }
-    }
-    @Preview
-    @Composable
-    fun PreviewCounter() {
-        val counterState = remember { mutableIntStateOf(0) }
-
-        Counter(
-            count = counterState.intValue,
-            updateCount = { newCount ->
-                counterState.intValue = newCount
-            }
-        )
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-@Composable private fun Greeting(name: String) {}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt
deleted file mode 100644
index 928fce9..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress(
-    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "SimplifyBooleanWithConstants"
-)
-
-package androidx.compose.integration.docs.sideeffects
-
-import android.annotation.SuppressLint
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material.Button
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Scaffold
-import androidx.compose.material.ScaffoldState
-import androidx.compose.material.Text
-import androidx.compose.material.rememberScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.produceState
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.LifecycleOwner
-import kotlin.Boolean
-import kotlin.Exception
-import kotlin.Long
-import kotlin.Nothing
-import kotlin.String
-import kotlin.Suppress
-import kotlin.Unit
-import kotlin.random.Random
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/side-effects
- *
- * No action required if it's modified.
- */
-
-@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
-@ExperimentalMaterialApi
-private object SideEffectsSnippet1 {
-    @Composable
-    fun MyScreen(
-        state: UiState<List<Movie>>,
-        scaffoldState: ScaffoldState = rememberScaffoldState()
-    ) {
-
-        // If the UI state contains an error, show snackbar
-        if (state.hasError) {
-
-            // `LaunchedEffect` will cancel and re-launch if
-            // `scaffoldState.snackbarHostState` changes
-            LaunchedEffect(scaffoldState.snackbarHostState) {
-                // Show snackbar using a coroutine, when the coroutine is cancelled the
-                // snackbar will automatically dismiss. This coroutine will cancel whenever
-                // `state.hasError` is false, and only start when `state.hasError` is true
-                // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
-                scaffoldState.snackbarHostState.showSnackbar(
-                    message = "Error message",
-                    actionLabel = "Retry message"
-                )
-            }
-        }
-
-        Scaffold(scaffoldState = scaffoldState) {
-            /* ... */
-        }
-    }
-}
-
-@ExperimentalMaterialApi
-private object SideEffectsSnippet2 {
-    @Composable
-    fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {
-
-        // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
-        val scope = rememberCoroutineScope()
-
-        Scaffold(scaffoldState = scaffoldState) { innerPadding ->
-            Column(Modifier.padding(innerPadding)) {
-                /* ... */
-                Button(
-                    onClick = {
-                        // Create a new coroutine in the event handler to show a snackbar
-                        scope.launch {
-                            scaffoldState.snackbarHostState.showSnackbar("Something happened!")
-                        }
-                    }
-                ) {
-                    Text("Press me")
-                }
-            }
-        }
-    }
-}
-
-private object SideEffectsSnippet3 {
-    @Composable
-    fun LandingScreen(onTimeout: () -> Unit) {
-
-        // This will always refer to the latest onTimeout function that
-        // LandingScreen was recomposed with
-        val currentOnTimeout by rememberUpdatedState(onTimeout)
-
-        // Create an effect that matches the lifecycle of LandingScreen.
-        // If LandingScreen recomposes, the delay shouldn't start again.
-        LaunchedEffect(true) {
-            delay(SplashWaitTimeMillis)
-            currentOnTimeout()
-        }
-
-        /* Landing screen content */
-    }
-}
-
-private object SideEffectsSnippet4 {
-    @Composable
-    fun HomeScreen(
-        lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-        onStart: () -> Unit, // Send the 'started' analytics event
-        onStop: () -> Unit // Send the 'stopped' analytics event
-    ) {
-        // Safely update the current lambdas when a new one is provided
-        val currentOnStart by rememberUpdatedState(onStart)
-        val currentOnStop by rememberUpdatedState(onStop)
-
-        // If `lifecycleOwner` changes, dispose and reset the effect
-        DisposableEffect(lifecycleOwner) {
-            // Create an observer that triggers our remembered callbacks
-            // for sending analytics events
-            val observer = LifecycleEventObserver { _, event ->
-                if (event == Lifecycle.Event.ON_START) {
-                    currentOnStart()
-                } else if (event == Lifecycle.Event.ON_STOP) {
-                    currentOnStop()
-                }
-            }
-
-            // Add the observer to the lifecycle
-            lifecycleOwner.lifecycle.addObserver(observer)
-
-            // When the effect leaves the Composition, remove the observer
-            onDispose {
-                lifecycleOwner.lifecycle.removeObserver(observer)
-            }
-        }
-
-        /* Home screen content */
-    }
-}
-
-private object SideEffectsSnippet5 {
-    @Composable
-    fun rememberAnalytics(user: User): FirebaseAnalytics {
-        val analytics: FirebaseAnalytics = remember {
-            // START - DO NOT COPY IN CODE SNIPPET
-            FirebaseAnalytics()
-            // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
-        }
-
-        // On every successful composition, update FirebaseAnalytics with
-        // the userType from the current User, ensuring that future analytics
-        // events have this metadata attached
-        SideEffect {
-            analytics.setUserProperty("userType", user.userType)
-        }
-        return analytics
-    }
-}
-
-private object SideEffectsSnippet6 {
-    @Composable
-    fun loadNetworkImage(
-        url: String,
-        imageRepository: ImageRepository
-    ): State<Result<Image>> {
-
-        // Creates a State<T> with Result.Loading as initial value
-        // If either `url` or `imageRepository` changes, the running producer
-        // will cancel and will be re-launched with the new inputs.
-        return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
-
-            // In a coroutine, can make suspend calls
-            val image = imageRepository.load(url)
-
-            // Update State with either an Error or Success result.
-            // This will trigger a recomposition where this State is read
-            value = if (image == null) {
-                Result.Error
-            } else {
-                Result.Success(image)
-            }
-        }
-    }
-}
-
-private object SideEffectsSnippet7 {
-    @Composable
-    fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) {
-
-        val todoTasks = remember { mutableStateListOf<String>() }
-
-        // Calculate high priority tasks only when the todoTasks or highPriorityKeywords
-        // change, not on every recomposition
-        val highPriorityTasks by remember(highPriorityKeywords) {
-            derivedStateOf { todoTasks.filter { it.containsWord(highPriorityKeywords) } }
-        }
-
-        Box(Modifier.fillMaxSize()) {
-            LazyColumn {
-                items(highPriorityTasks) { /* ... */ }
-                items(todoTasks) { /* ... */ }
-            }
-            /* Rest of the UI where users can add elements to the list */
-        }
-    }
-}
-
-@Composable
-private fun SideEffectsSnippet8(messages: List<Message>) {
-    val listState = rememberLazyListState()
-
-    LazyColumn(state = listState) {
-        // ...
-    }
-
-    LaunchedEffect(listState) {
-        snapshotFlow { listState.firstVisibleItemIndex }
-            .map { index -> index > 0 }
-            .distinctUntilChanged()
-            .filter { it == true }
-            .collect {
-                MyAnalyticsService.sendScrolledPastFirstItemEvent()
-            }
-    }
-}
-
-private object SideEffectsSnippet9 {
-    @Composable
-    fun HomeScreen(
-        lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-        onStart: () -> Unit, // Send the 'started' analytics event
-        onStop: () -> Unit // Send the 'stopped' analytics event
-    ) {
-        // These values never change in Composition
-        val currentOnStart by rememberUpdatedState(onStart)
-        val currentOnStop by rememberUpdatedState(onStop)
-
-        DisposableEffect(lifecycleOwner) {
-            val observer = LifecycleEventObserver { _, event ->
-                // START - DO NOT COPY IN CODE SNIPPET
-                if (event == Lifecycle.Event.ON_START) {
-                    currentOnStart()
-                } else if (event == Lifecycle.Event.ON_STOP) {
-                    currentOnStop()
-                }
-                // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
-            }
-
-            lifecycleOwner.lifecycle.addObserver(observer)
-            onDispose {
-                lifecycleOwner.lifecycle.removeObserver(observer)
-            }
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private const val SplashWaitTimeMillis = 1000L
-
-private data class Movie(val id: Long, val url: String = "")
-
-private data class UiState<T>(
-    val loading: Boolean = false,
-    val exception: Exception? = null,
-    val data: T? = null
-) {
-    val hasError: Boolean
-        get() = exception != null
-}
-
-private class Message(val id: Long)
-private class Image
-private class ImageRepository {
-    fun load(url: String): Image? = if (Random.nextInt() == 0) Image() else null // Avoid warnings
-}
-
-private class FirebaseAnalytics {
-    fun setUserProperty(name: String, value: String) {}
-}
-
-private sealed class Result<out R> {
-    data class Success<out T>(val data: T) : Result<T>()
-    object Loading : Result<Nothing>()
-    object Error : Result<Nothing>()
-}
-
-private class User(val userType: String = "user")
-private class Weather
-private class Greeting(val name: String)
-private fun prepareGreeting(user: User, weather: Weather) = Greeting("haha")
-
-private fun String.containsWord(input: List<String>): Boolean = false
-
-private object MyAnalyticsService {
-    fun sendScrolledPastFirstItemEvent() = Unit
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
deleted file mode 100644
index bda62b5..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
+++ /dev/null
@@ -1,410 +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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
-
-package androidx.compose.integration.docs.testing
-
-import android.os.Build
-import android.view.KeyEvent as AndroidKeyEvent
-import android.view.KeyEvent.ACTION_DOWN as ActionDown
-import android.view.KeyEvent.KEYCODE_A as KeyCodeA
-import androidx.activity.ComponentActivity
-import androidx.annotation.RequiresApi
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.layout.FirstBaseline
-import androidx.compose.ui.semantics.ProgressBarRangeInfo
-import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.IdlingResource
-import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertAll
-import androidx.compose.ui.test.assertAny
-import androidx.compose.ui.test.assertContentDescriptionContains
-import androidx.compose.ui.test.assertContentDescriptionEquals
-import androidx.compose.ui.test.assertCountEquals
-import androidx.compose.ui.test.assertHasClickAction
-import androidx.compose.ui.test.assertHasNoClickAction
-import androidx.compose.ui.test.assertHeightIsAtLeast
-import androidx.compose.ui.test.assertHeightIsEqualTo
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEnabled
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.assertIsNotDisplayed
-import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.assertIsNotFocused
-import androidx.compose.ui.test.assertIsNotSelected
-import androidx.compose.ui.test.assertIsOff
-import androidx.compose.ui.test.assertIsOn
-import androidx.compose.ui.test.assertIsSelectable
-import androidx.compose.ui.test.assertIsSelected
-import androidx.compose.ui.test.assertIsToggleable
-import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertRangeInfoEquals
-import androidx.compose.ui.test.assertTextContains
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertValueEquals
-import androidx.compose.ui.test.assertWidthIsAtLeast
-import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.doubleClick
-import androidx.compose.ui.test.filter
-import androidx.compose.ui.test.filterToOne
-import androidx.compose.ui.test.getAlignmentLinePosition
-import androidx.compose.ui.test.getUnclippedBoundsInRoot
-import androidx.compose.ui.test.hasAnyAncestor
-import androidx.compose.ui.test.hasAnyChild
-import androidx.compose.ui.test.hasAnyDescendant
-import androidx.compose.ui.test.hasAnySibling
-import androidx.compose.ui.test.hasClickAction
-import androidx.compose.ui.test.hasContentDescription
-import androidx.compose.ui.test.hasImeAction
-import androidx.compose.ui.test.hasNoClickAction
-import androidx.compose.ui.test.hasNoScrollAction
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasProgressBarRangeInfo
-import androidx.compose.ui.test.hasScrollAction
-import androidx.compose.ui.test.hasSetTextAction
-import androidx.compose.ui.test.hasStateDescription
-import androidx.compose.ui.test.hasTestTag
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.isDialog
-import androidx.compose.ui.test.isEnabled
-import androidx.compose.ui.test.isFocusable
-import androidx.compose.ui.test.isFocused
-import androidx.compose.ui.test.isHeading
-import androidx.compose.ui.test.isNotEnabled
-import androidx.compose.ui.test.isNotFocusable
-import androidx.compose.ui.test.isNotFocused
-import androidx.compose.ui.test.isNotSelected
-import androidx.compose.ui.test.isOff
-import androidx.compose.ui.test.isOn
-import androidx.compose.ui.test.isPopup
-import androidx.compose.ui.test.isRoot
-import androidx.compose.ui.test.isSelectable
-import androidx.compose.ui.test.isSelected
-import androidx.compose.ui.test.isToggleable
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.longClick
-import androidx.compose.ui.test.multiTouchSwipe
-import androidx.compose.ui.test.onAllNodesWithContentDescription
-import androidx.compose.ui.test.onAllNodesWithTag
-import androidx.compose.ui.test.onAllNodesWithText
-import androidx.compose.ui.test.onAncestors
-import androidx.compose.ui.test.onChild
-import androidx.compose.ui.test.onChildAt
-import androidx.compose.ui.test.onChildren
-import androidx.compose.ui.test.onFirst
-import androidx.compose.ui.test.onLast
-import androidx.compose.ui.test.onNodeWithContentDescription
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.onParent
-import androidx.compose.ui.test.onRoot
-import androidx.compose.ui.test.onSibling
-import androidx.compose.ui.test.onSiblings
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performImeAction
-import androidx.compose.ui.test.performKeyPress
-import androidx.compose.ui.test.performScrollTo
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTextClearance
-import androidx.compose.ui.test.performTextInput
-import androidx.compose.ui.test.performTextReplacement
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.pinch
-import androidx.compose.ui.test.printToLog
-import androidx.compose.ui.test.printToString
-import androidx.compose.ui.test.swipe
-import androidx.compose.ui.test.swipeDown
-import androidx.compose.ui.test.swipeLeft
-import androidx.compose.ui.test.swipeRight
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.test.swipeWithVelocity
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/testing-cheatsheet.html
- *
- * No action required if it's modified.
- */
-
-@Composable
-private fun TestingCheatSheetFinders() {
-    // FINDERS
-    composeTestRule.onNode(matcher)
-    composeTestRule.onAllNodes(matcher)
-    composeTestRule.onNodeWithContentDescription("label")
-    composeTestRule.onAllNodesWithContentDescription("label")
-    composeTestRule.onNodeWithTag("tag")
-    composeTestRule.onAllNodesWithTag("tag")
-    composeTestRule.onNodeWithText("text")
-    composeTestRule.onAllNodesWithText("text")
-    composeTestRule.onRoot()
-
-    // OPTIONS
-    composeTestRule.onNode(matcher, useUnmergedTree = true)
-
-    // SELECTORS
-    composeTestRule.onAllNodes(matcher)
-        .filter(matcher)
-        .filterToOne(matcher)
-    composeTestRule.onNode(matcher)
-        .onAncestors()
-    composeTestRule.onNode(matcher)
-        .onChild()
-        .onChildAt(0)
-        .onChildren()
-        .onFirst()
-    composeTestRule.onAllNodes(matcher)
-        .onLast()
-        .onParent()
-        .onSibling()
-        .onSiblings()
-
-    // HIERARCHICAL
-    composeTestRule.onNode(
-        hasAnyAncestor(matcher) and
-            hasAnyChild(matcher) and
-            hasAnyDescendant(matcher) and
-            hasAnySibling(matcher) and
-            hasParent(matcher)
-    )
-
-    // MATCHERS
-    composeTestRule.onNode(
-        hasClickAction() and
-            hasNoClickAction() and
-            hasContentDescription("label") and
-            hasImeAction(ImeAction.Default) and
-            hasProgressBarRangeInfo(rangeInfo) and
-            hasScrollAction() and
-            hasNoScrollAction() and
-            hasSetTextAction() and
-            hasStateDescription("label") and
-            hasTestTag("tag") and
-            hasText("text") and
-            isDialog() and
-            isEnabled() and
-            isFocusable() and
-            isFocused() and
-            isHeading() and
-            isNotEnabled() and
-            isNotFocusable() and
-            isNotFocused() and
-            isNotSelected() and
-            isOff() and
-            isOn() and
-            isPopup() and
-            isRoot() and
-            isSelectable() and
-            isSelected() and
-            isToggleable()
-    )
-}
-
-@Composable
-private fun TestingCheatSheetActions() {
-    composeTestRule.onRoot()
-        .performClick()
-        .performTouchInput { longClick() }
-        .performScrollTo()
-        .performSemanticsAction(SemanticsActions.OnLongClick)
-    composeTestRule.onRoot()
-        .performKeyPress(keyEvent2)
-    composeTestRule.onRoot()
-        .performImeAction()
-    composeTestRule.onRoot()
-        .performTextClearance()
-    composeTestRule.onRoot()
-        .performTextInput("text")
-    composeTestRule.onRoot()
-        .performTextReplacement("text")
-
-    // GESTURES
-
-    composeTestRule.onRoot().performTouchInput {
-        click()
-        longClick()
-        doubleClick()
-        swipe(this.center, offset)
-        swipe({ Offset(it.toFloat(), it.toFloat()) }, 1L)
-        @OptIn(ExperimentalTestApi::class)
-        multiTouchSwipe(listOf { Offset(it.toFloat(), it.toFloat()) }, 1L)
-        pinch(offset, offset, offset, offset)
-        swipeWithVelocity(offset, offset, 1f)
-        swipeUp()
-        swipeDown()
-        swipeLeft()
-        swipeRight()
-
-        // PARTIAL GESTURES
-        down(offset)
-        moveTo(offset)
-        updatePointerTo(0, offset)
-        moveBy(offset)
-        updatePointerBy(0, offset)
-        move()
-        percentOffset()
-        up()
-        cancel()
-
-        currentPosition(0)
-        advanceEventTime(1L)
-
-        eventPeriodMillis
-        visibleSize
-
-        bottom
-        bottomCenter
-        bottomLeft
-        bottomRight
-        center
-        centerLeft
-        centerRight
-        centerX
-        centerY
-        height
-        left
-        right
-        top
-        topCenter
-        topLeft
-        topRight
-        width
-    }
-}
-
-@Composable
-private fun TestingCheatSheetAssertions() {
-    composeTestRule.onRoot().apply {
-        assert(matcher)
-        assertContentDescriptionContains("label")
-        assertContentDescriptionEquals("label")
-        assertHasClickAction()
-        assertHasNoClickAction()
-        assertIsDisplayed()
-        assertIsEnabled()
-        assertIsFocused()
-        assertIsNotDisplayed()
-        assertIsNotEnabled()
-        assertIsNotFocused()
-        assertIsNotSelected()
-        assertIsOff()
-        assertIsOn()
-        assertIsSelectable()
-        assertIsSelected()
-        assertIsToggleable()
-        assertRangeInfoEquals(rangeInfo)
-        assertTextContains("text")
-        assertTextEquals("text")
-        assertValueEquals("value")
-    }
-
-    composeTestRule.onRoot().apply {
-        assertDoesNotExist()
-        assertExists()
-    }
-
-    // COLLECTIONS
-    composeTestRule.onAllNodes(matcher)
-        .assertAll(matcher)
-        .assertAny(matcher)
-        .assertCountEquals(1)
-
-    // BOUNDS
-    composeTestRule.onRoot()
-        .assertWidthIsEqualTo(1.dp)
-        .assertHeightIsEqualTo(1.dp)
-        .assertWidthIsAtLeast(1.dp)
-        .assertHeightIsAtLeast(1.dp)
-        .assertPositionInRootIsEqualTo(1.dp, 1.dp)
-        .assertTopPositionInRootIsEqualTo(1.dp)
-        .assertLeftPositionInRootIsEqualTo(1.dp)
-
-    composeTestRule.onNodeWithTag("button")
-        .getAlignmentLinePosition(FirstBaseline)
-
-    composeTestRule.onRoot()
-        .getUnclippedBoundsInRoot()
-}
-
-@OptIn(DelicateCoroutinesApi::class)
-@RequiresApi(Build.VERSION_CODES.O)
-private fun TestingCheatSheetOther() {
-
-    // COMPOSE TEST RULE
-    nonAndroidComposeTestRule.apply {
-        setContent { }
-        density
-        runOnIdle { }
-        runOnUiThread { }
-        waitForIdle()
-        waitUntil { true }
-        mainClock.apply {
-            autoAdvance
-            currentTime
-            advanceTimeBy(1L)
-            advanceTimeByFrame()
-            advanceTimeUntil { true }
-        }
-        registerIdlingResource(idlingResource)
-        unregisterIdlingResource(idlingResource)
-    }
-    GlobalScope.launch {
-        nonAndroidComposeTestRule.awaitIdle()
-    }
-
-    // ANDROID COMPOSE TEST RULE
-    composeTestRule.activity
-    composeTestRule.activityRule
-
-    // Capture and debug
-    composeTestRule.onRoot().apply {
-        printToLog("TAG")
-        printToString()
-        captureToImage()
-    }
-    // MATCHERS
-    matcher.matches(composeTestRule.onRoot().fetchSemanticsNode())
-}
-
-/*
-Fakes needed for snippets to build:
- */
-private val matcher = isDialog()
-private class FakeActivity : ComponentActivity()
-private val composeTestRule = createAndroidComposeRule<FakeActivity>()
-private val nonAndroidComposeTestRule = createComposeRule()
-private val keyEvent2 = KeyEvent(AndroidKeyEvent(ActionDown, KeyCodeA))
-private val offset = Offset(0f, 0f)
-private val rangeInfo = ProgressBarRangeInfo(0f, 0f..1f)
-private val idlingResource = object : IdlingResource {
-    override val isIdleNow: Boolean
-        get() = TODO("Stub!")
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt
deleted file mode 100644
index ab880fe..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt
+++ /dev/null
@@ -1,415 +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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
-
-package androidx.compose.integration.docs.testing
-
-import android.view.KeyEvent as AndroidKeyEvent
-import android.view.KeyEvent.ACTION_DOWN as ActionDown
-import android.view.KeyEvent.KEYCODE_A as KeyCodeA
-import androidx.activity.ComponentActivity
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.integration.docs.testing.CreateSemanticsPropertySnippet.PickedDateKey
-import androidx.compose.material.Scaffold
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.AccessibilityAction
-import androidx.compose.ui.semantics.SemanticsPropertyKey
-import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.testTagsAsResourceId
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.IdlingResource
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertAll
-import androidx.compose.ui.test.assertAny
-import androidx.compose.ui.test.assertCountEquals
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.filter
-import androidx.compose.ui.test.hasAnyAncestor
-import androidx.compose.ui.test.hasAnyDescendant
-import androidx.compose.ui.test.hasAnySibling
-import androidx.compose.ui.test.hasClickAction
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasTestTag
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onAllNodesWithContentDescription
-import androidx.compose.ui.test.onChildren
-import androidx.compose.ui.test.onFirst
-import androidx.compose.ui.test.onNodeWithContentDescription
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.onRoot
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyPress
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.performTouchInput
-import androidx.compose.ui.test.printToLog
-import androidx.compose.ui.test.swipeLeft
-import androidx.test.espresso.Espresso
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
-import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.UiObject2
-import kotlinx.coroutines.flow.MutableStateFlow
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/testing
- *
- * No action required if it's modified.
- */
-
-@Composable
-private fun ButtonSnippet() {
-    MyButton(modifier = Modifier.semantics { contentDescription = "Like button" })
-}
-
-private object ComposeTestRuleSnippet {
-    // file: app/src/androidTest/kotlin/com/package/MyComposeTest.kt
-
-    class MyComposeTest {
-
-        @get:Rule
-        val composeTestRule = createAndroidComposeRule<MyActivity>()
-        // createComposeRule() if you don't need access to the activityTestRule
-
-        @Test fun myTest() {
-            // Start the app
-            composeTestRule.setContent {
-                MyAppTheme {
-                    MainScreen(uiState = exampleUiState, /*...*/)
-                }
-            }
-
-            composeTestRule.onNodeWithText("Continue").performClick()
-
-            composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
-        }
-    }
-}
-
-@Composable
-private fun SelectNodesSnippets() {
-    // single node
-    // It's API, see line below.
-    // onNode(<<SemanticsMatcher>>, useUnmergedTree = false): SemanticsNodeInteraction
-    composeTestRule
-        .onNode(hasText("Button")) // Equivalent to onNodeWithText("Button")
-
-    // multiple nodes
-    // It's API, see line below.
-    // onAllNodes(<<SemanticsMatcher>>): SemanticsNodeInteractionCollection
-
-    // Example
-    composeTestRule
-        .onAllNodes(hasText("Button")) // Equivalent to onAllNodesWithText("Button")
-}
-
-@Composable
-private fun MergeTextSnippet() {
-    MyButton {
-        Text("Hello")
-        Text("World")
-    }
-
-    composeTestRule.onRoot().printToLog("TAG")
-
-    composeTestRule.onRoot(useUnmergedTree = true).printToLog("TAG")
-}
-
-@Composable
-private fun UseUnmergedTreeSnippet() {
-    composeTestRule.onNodeWithText("World", useUnmergedTree = true).assertIsDisplayed()
-}
-
-// assertions
-
-@Composable
-private fun CheckAssertionsOneNodeSnippet() {
-    // Single matcher:
-    composeTestRule.onNode(matcher).assert(hasText("Button")) // hasText is a SemanticsMatcher
-
-    // Multiple matchers can use and / or
-    composeTestRule.onNode(matcher)
-        .assert(hasText("Button") or hasText("Button2"))
-}
-
-@Composable
-private fun CheckAssertionsMultipleNodesSnippet() {
-    // Check number of matched nodes
-    composeTestRule
-        .onAllNodesWithContentDescription("Beatle").assertCountEquals(4)
-    // At least one matches
-    composeTestRule
-        .onAllNodesWithContentDescription("Beatle").assertAny(hasTestTag("Drummer"))
-    // All of them match
-    composeTestRule
-        .onAllNodesWithContentDescription("Beatle").assertAll(hasClickAction())
-}
-
-@Composable
-private fun SemanticsNodeInteraction.PerformClickEtc() {
-    val listOfActions = listOf(
-        // start snippet
-        performClick(),
-        performSemanticsAction(key),
-        performKeyPress(keyEvent),
-        performTouchInput { swipeLeft() }
-        // end snippet
-    )
-}
-
-@Composable
-private fun HierarchicalApiSnippets() {
-    // It's API, look for changes below.
-    val matcher = SemanticsMatcher("test", { true })
-    hasParent(matcher)
-    hasAnySibling(matcher)
-    hasAnyAncestor(matcher)
-    hasAnyDescendant(matcher)
-}
-
-@Composable
-private fun AssertIsDisplayedSnippet() {
-    composeTestRule.onNode(hasParent(hasText("Button")))
-        .assertIsDisplayed()
-}
-
-@Composable
-private fun SelectorsSnippet() {
-    composeTestRule.onNode(hasTestTag("Players"))
-        .onChildren()
-        .filter(hasClickAction())
-        .assertCountEquals(4)
-        .onFirst()
-        .assert(hasText("John"))
-}
-
-private object SyncSnippet {
-    @Test fun counterTest() {
-        var myCounter by mutableIntStateOf(0) // State that can cause recompositions
-        var lastSeenValue = 0 // Used to track recompositions
-        composeTestRule.setContent {
-            Text(myCounter.toString())
-            lastSeenValue = myCounter
-        }
-        myCounter = 1 // The state changes, but there is no recomposition
-
-        // Fails because nothing triggered a recomposition
-        assertTrue(lastSeenValue == 1)
-
-        // Passes because the assertion triggers recomposition
-        composeTestRule.onNodeWithText("1").assertExists()
-    }
-}
-
-private fun TestClockAdvanceSnippets() {
-    composeTestRule.mainClock.autoAdvance = false
-
-    composeTestRule.mainClock.advanceTimeByFrame()
-    composeTestRule.mainClock.advanceTimeBy(milliseconds)
-}
-
-private fun IdlingResourceSnippet() {
-    composeTestRule.registerIdlingResource(idlingResource)
-    composeTestRule.unregisterIdlingResource(idlingResource)
-}
-
-private fun ManualSyncSnippet() {
-    composeTestRule.mainClock.autoAdvance = true // default
-    composeTestRule.waitForIdle() // Advances the clock until Compose is idle
-
-    composeTestRule.mainClock.autoAdvance = false
-    composeTestRule.waitForIdle() // Only waits for Idling Resources to become idle
-}
-
-private fun AdvanceWaitSnippets() {
-    composeTestRule.mainClock.advanceTimeUntil(timeoutMs) { condition }
-
-    composeTestRule.waitUntil(timeoutMs) { condition }
-}
-
-private object ComponentActivitySnippet {
-    class MyComposeTest {
-
-        @get:Rule
-        val composeTestRule = createAndroidComposeRule<ComponentActivity>()
-
-        @Test
-        fun myTest() {
-            // Start the app
-            composeTestRule.setContent {
-                MyAppTheme {
-                    MainScreen(uiState = exampleUiState, /*...*/)
-                }
-            }
-            val continueLabel = composeTestRule.activity.resources.getString(R.string.next)
-            composeTestRule.onNodeWithText(continueLabel).performClick()
-        }
-    }
-}
-
-private object CreateSemanticsPropertySnippet {
-    // Creates a Semantics property of type boolean
-    val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
-    var SemanticsPropertyReceiver.pickedDate by PickedDateKey
-}
-
-private fun UseSemanticsPropertySnippet() {
-    composeTestRule
-        .onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
-        .assertExists()
-}
-
-private object StateRestorationSnippet {
-    @OptIn(ExperimentalTestApi::class)
-    class MyStateRestorationTests {
-
-        @get:Rule
-        val composeTestRule = createComposeRule()
-
-        @Test
-        fun onRecreation_stateIsRestored() {
-            val restorationTester = StateRestorationTester(composeTestRule)
-
-            restorationTester.setContent { MainScreen() }
-
-            // TODO: Run actions that modify the state
-
-            // Trigger a recreation
-            restorationTester.emulateSavedInstanceStateRestore()
-
-            // TODO: Verify that state has been correctly restored.
-        }
-    }
-}
-
-private object InteropTestSnippet {
-    @Test fun androidViewInteropTest() {
-        // Check the initial state of a TextView that depends on a Compose state:
-        Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
-        // Click on the Compose button that changes the state
-        composeTestRule.onNodeWithText("Click here").performClick()
-        // Check the new value
-        Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
-    }
-}
-
-@ExperimentalComposeUiApi
-@Composable
-fun UiAutomatorInteropTestSnippet() {
-    Scaffold(
-        // Enables for all composables in the hierarchy.
-        modifier = Modifier.semantics {
-            testTagsAsResourceId = true
-        }
-    ) { padding ->
-        // Modifier.testTag is accessible from UiAutomator for composables nested here.
-        LazyColumn(
-            modifier = Modifier
-                .testTag("myLazyColumn")
-                .padding(padding),
-        ) {
-            // content
-        }
-    }
-
-    val device = UiDevice.getInstance(getInstrumentation())
-    val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
-    // some interaction with the lazyColumn
-}
-
-private object TestingSnippets13 {
-    class MyTest() {
-
-        private val themeIsDark = MutableStateFlow(false)
-
-        @Before
-        fun setUp() {
-            composeTestRule.setContent {
-                JetchatTheme(
-                    isDarkTheme = themeIsDark.collectAsState(false).value
-                ) {
-                    MainScreen()
-                }
-            }
-        }
-
-        @Test fun changeTheme_scrollIsPersisted() {
-            composeTestRule.onNodeWithContentDescription("Continue").performClick()
-
-            // Set theme to dark
-            themeIsDark.value = true
-
-            // Check that we're still on the same page
-            composeTestRule.onNodeWithContentDescription("Welcome").assertIsDisplayed()
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private val matcher = hasText("Button")
-private val text = ""
-private val composeTestRule = createAndroidComposeRule<MyActivity>()
-@Composable private fun MyButton(modifier: Modifier) {}
-@Composable private fun MyAppTheme(content: @Composable () -> Unit) {}
-@Composable private fun JetchatTheme(isDarkTheme: Boolean, content: @Composable () -> Unit) {}
-private val exampleUiState = Unit
-@Composable private fun MainScreen(uiState: Any = Unit) {}
-private class MyActivity : ComponentActivity()
-@Composable private fun MyButton(content: @Composable RowScope.() -> Unit) { }
-private lateinit var key: SemanticsPropertyKey<AccessibilityAction<() -> Boolean>>
-private var keyEvent = KeyEvent(AndroidKeyEvent(ActionDown, KeyCodeA))
-private const val milliseconds = 10L
-private const val timeoutMs = 10L
-private val idlingResource = object : IdlingResource {
-    override val isIdleNow: Boolean
-        get() = TODO("Stub!")
-}
-private val condition = true
-private object R {
-    object string {
-        const val next = 1
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tooling/Tooling.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tooling/Tooling.kt
deleted file mode 100644
index c415400..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tooling/Tooling.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
-* Copyright 2021 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.
-*/
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "LocalVariableName")
-
-package androidx.compose.integration.docs.tooling
-
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.tooling.preview.PreviewParameter
-import androidx.compose.ui.tooling.preview.PreviewParameterProvider
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/tooling
- *
- * No action required if it's modified.
- */
-
-private object ToolingSnippet1 {
-    @Composable
-    fun SimpleComposable() {
-        Text("Hello World")
-    }
-}
-
-private class ToolingSnippet2 {
-    @Preview
-    @Composable
-    fun ComposablePreview() {
-        SimpleComposable()
-    }
-}
-
-@Composable
-private fun ToolingSnippetLocalInspectionMode() {
-    if (LocalInspectionMode.current) {
-        // Show this text in a preview window:
-        Text("Hello preview user!")
-    } else {
-        // Show this text in the app:
-        Text("Hello $name!")
-    }
-}
-
-private class ToolingSnippetMultipreviewDefinition {
-    @Preview(
-        name = "small font",
-        group = "font scales",
-        fontScale = 0.5f
-    )
-    @Preview(
-        name = "large font",
-        group = "font scales",
-        fontScale = 1.5f
-    )
-    annotation class FontScalePreviews
-}
-
-private class ToolingSnippetMultipreviewUsage {
-    @FontScalePreviews
-    @Composable
-    fun HelloWorldPreview() {
-        Text("Hello World")
-    }
-}
-
-private class ToolingSnippetMultipreviewCombine {
-    @Preview(
-        name = "dark theme",
-        group = "themes",
-        uiMode = UI_MODE_NIGHT_YES
-    )
-    @FontScalePreviews
-    @DevicePreviews
-    annotation class CompletePreviews
-
-    @CompletePreviews
-    @Composable
-    fun HelloWorldPreview() {
-        MyTheme { Surface { Text("Hello world") } }
-    }
-}
-
-private class ToolingSnippet3 {
-    @Preview(showBackground = true, backgroundColor = 0xFF00FF00)
-    @Composable
-    fun WithGreenBackground() {
-        Text("Hello World")
-    }
-}
-
-private class ToolingSnippet4 {
-    @Preview(widthDp = 50, heightDp = 50)
-    @Composable
-    fun SquareComposablePreview() {
-        Box(Modifier.background(Color.Yellow)) {
-            Text("Hello World")
-        }
-    }
-}
-
-private class ToolingSnippet5 {
-    @Preview(locale = "fr-rFR")
-    @Composable
-    fun DifferentLocaleComposablePreview() {
-        Text(text = stringResource(R.string.greetings))
-    }
-}
-
-private class ToolingSnippet6 {
-    @Preview(showSystemUi = true)
-    @Composable
-    fun DecoratedComposablePreview() {
-        Text("Hello World")
-    }
-}
-
-private class ToolingSnippet7 {
-    @Preview
-    @Composable
-    fun UserProfilePreview(
-        @PreviewParameter(UserPreviewParameterProvider::class) user: User
-    ) {
-        UserProfile(user)
-    }
-
-    class UserPreviewParameterProvider : PreviewParameterProvider<User> {
-        override val values = sequenceOf(
-            User("Elise"),
-            User("Frank"),
-            User("Julia")
-        )
-    }
-}
-
-private class ToolingSnippet8 {
-    @Preview
-    @Composable
-    fun UserProfilePreview(
-        @PreviewParameter(UserPreviewParameterProvider::class, limit = 2) user: User
-    ) {
-        UserProfile(user)
-    }
-}
-
-private fun SimpleComposable() {}
-private data class User(val name: String)
-
-@Composable
-private fun UserProfile(user: User) {
-    Text(user.name)
-}
-
-private class UserPreviewParameterProvider : PreviewParameterProvider<User> {
-    override val values = emptySequence<User>()
-}
-
-private enum class SurfaceState { Released, Pressed }
-
-private object R {
-    object string {
-        const val greetings = 1
-    }
-}
-
-private annotation class FontScalePreviews
-private annotation class DevicePreviews
-
-@Composable
-private fun MyTheme(content: @Composable () -> Unit) {}
-
-/*
- * Fakes needed for snippets to build:
- */
-
-private val name = "friend"
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt
deleted file mode 100644
index 636f2d1..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/tutorial/Tutorial.kt
+++ /dev/null
@@ -1,369 +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.integration.docs.tutorial
-
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/tutorial
- *
- * No action required if it's modified.
- *
- * Tech writers: on DAC, these snippets contain html formatting that is omitted here.
- */
-
-private object TutorialSnippet1 {
-    class MainActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            setContent {
-                Text("Hello world!")
-            }
-        }
-    }
-}
-
-/*
-Page 2
- */
-
-private object TutorialSnippet2 {
-    class MainActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            setContent {
-                Greeting("Android")
-            }
-        }
-    }
-
-    @Composable
-    fun Greeting(name: String) {
-        Text(text = "Hello $name!")
-    }
-}
-
-/*
-Page 3
- */
-
-private object TutorialSnippet3 {
-    @Composable
-    fun Greeting(name: String) {
-        Text(text = "Hello $name!")
-    }
-
-    @Preview
-    @Composable
-    fun PreviewGreeting() {
-        Greeting("Android")
-    }
-}
-
-/*
-Lesson 2
- */
-
-private object TutorialSnippet4 {
-    class MainActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            setContent {
-                NewsStory()
-            }
-        }
-    }
-
-    @Composable
-    fun NewsStory() {
-        Text("A day in Shark Fin Cove")
-        Text("Davenport, California")
-        Text("December 2018")
-    }
-
-    @Preview
-    @Composable
-    fun DefaultPreview() {
-        NewsStory()
-    }
-}
-
-/*
-Page 2
- */
-
-private object TutorialSnippet5 {
-    @Composable
-    fun NewsStory() {
-        Column {
-            Text("A day in Shark Fin Cove")
-            Text("Davenport, California")
-            Text("December 2018")
-        }
-    }
-}
-
-private object TutorialSnippet6 {
-    @Composable
-    fun NewsStory() {
-        Column(
-            modifier = Modifier.padding(16.dp)
-        ) {
-            Text("A day in Shark Fin Cove")
-            Text("Davenport, California")
-            Text("December 2018")
-        }
-    }
-}
-
-private object TutorialSnippet7 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-
-        Column(
-            modifier = Modifier.padding(16.dp)
-        ) {
-            Image(image, contentDescription = null)
-
-            Text("A day in Shark Fin Cove")
-            Text("Davenport, California")
-            Text("December 2018")
-        }
-    }
-}
-
-private object TutorialSnippet8 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        Column(
-            modifier = Modifier.padding(16.dp)
-        ) {
-            val imageModifier = Modifier
-                .height(180.dp)
-                .fillMaxWidth()
-
-            Image(
-                image,
-                contentDescription = null,
-                modifier = imageModifier,
-                contentScale = ContentScale.Crop
-            )
-
-            Text("A day in Shark Fin Cove")
-            Text("Davenport, California")
-            Text("December 2018")
-        }
-    }
-}
-
-/*
-Lesson 3
- */
-private object TutorialSnippet9 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        Column(
-            modifier = Modifier.padding(16.dp)
-        ) {
-            val imageModifier = Modifier
-                .height(180.dp)
-                .fillMaxWidth()
-                .clip(shape = RoundedCornerShape(4.dp))
-
-            Image(
-                image,
-                contentDescription = null,
-                modifier = imageModifier,
-                contentScale = ContentScale.Crop
-            )
-            Spacer(Modifier.height(16.dp))
-
-            Text("A day in Shark Fin Cove")
-            Text("Davenport, California")
-            Text("December 2018")
-        }
-    }
-}
-
-private object TutorialSnippet10 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        MaterialTheme {
-            Column(
-                modifier = Modifier.padding(16.dp)
-            ) {
-                val imageModifier = Modifier
-                    .height(180.dp)
-                    .fillMaxWidth()
-                    .clip(shape = RoundedCornerShape(4.dp))
-
-                Image(
-                    image,
-                    contentDescription = null,
-                    modifier = imageModifier,
-                    contentScale = ContentScale.Crop
-                )
-                Spacer(Modifier.height(16.dp))
-
-                Text("A day in Shark Fin Cove")
-                Text("Davenport, California")
-                Text("December 2018")
-            }
-        }
-    }
-}
-
-private object TutorialSnippet11 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        MaterialTheme {
-            val typography = MaterialTheme.typography
-            Column(
-                modifier = Modifier.padding(16.dp)
-            ) {
-                val imageModifier = Modifier
-                    .height(180.dp)
-                    .fillMaxWidth()
-                    .clip(shape = RoundedCornerShape(4.dp))
-
-                Image(
-                    image,
-                    contentDescription = null,
-                    modifier = imageModifier,
-                    contentScale = ContentScale.Crop
-                )
-                Spacer(Modifier.height(16.dp))
-
-                Text("A day in Shark Fin Cove",
-                    style = typography.h6)
-                Text("Davenport, California",
-                    style = typography.body2)
-                Text("December 2018",
-                    style = typography.body2)
-            }
-        }
-    }
-}
-
-private object TutorialSnippet12 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        MaterialTheme {
-            val typography = MaterialTheme.typography
-            Column(
-                modifier = Modifier.padding(16.dp)
-            ) {
-                val imageModifier = Modifier
-                    .height(180.dp)
-                    .fillMaxWidth()
-                    .clip(shape = RoundedCornerShape(4.dp))
-
-                Image(
-                    image,
-                    contentDescription = null,
-                    modifier = imageModifier,
-                    contentScale = ContentScale.Crop
-                )
-                Spacer(Modifier.height(16.dp))
-
-                Text(
-                    "A day wandering through the sandhills " +
-                        "in Shark Fin Cove, and a few of the " +
-                        "sights I saw",
-                    style = typography.h6)
-                Text("Davenport, California",
-                    style = typography.body2)
-                Text("December 2018",
-                    style = typography.body2)
-            }
-        }
-    }
-}
-
-/* ktlint-disable indent */
-private object TutorialSnippet13 {
-    @Composable
-    fun NewsStory() {
-        val image = painterResource(R.drawable.header)
-        MaterialTheme {
-            val typography = MaterialTheme.typography
-            Column(
-                modifier = Modifier.padding(16.dp)
-            ) {
-                val imageModifier = Modifier
-                    .height(180.dp)
-                    .fillMaxWidth()
-                    .clip(shape = RoundedCornerShape(4.dp))
-
-                Image(
-                    image, null,
-                    modifier = imageModifier,
-                    contentScale = ContentScale.Crop
-                )
-                Spacer(Modifier.height(16.dp))
-
-                Text(
-                    "A day wandering through the sandhills " +
-                        "in Shark Fin Cove, and a few of the " +
-                        "sights I saw",
-                    style = typography.h6,
-                    maxLines = 2,
-                    overflow = TextOverflow.Ellipsis)
-                Text("Davenport, California",
-                    style = typography.body2)
-                Text("December 2018",
-                    style = typography.body2)
-            }
-        }
-    }
-}
-
-/*
-Fakes needed for snippets to build:
- */
-
-private object R {
-    object drawable {
-        const val header = 1
-    }
-}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/tutorial/Tutorial.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/tutorial/Tutorial.kt
deleted file mode 100644
index b17b824..0000000
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/tutorial/Tutorial.kt
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-// Ignore lint warnings in documentation snippets
-@file:Suppress("unused", "UNUSED_PARAMETER")
-
-package androidx.compose.integration.tutorial
-
-import android.content.res.Configuration
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.animateContentSize
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.integration.tutorial.Lesson2_Layouts.Snippet1.Message
-import androidx.compose.integration.tutorial.Lesson2_Layouts.Snippet4.MessageCard
-import androidx.compose.integration.tutorial.Lesson4_ListsAnimations.Snippet1.Conversation
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
-
-/**
- * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/tutorial
- *
- * No action required if it's modified.
- */
-
-private object Lesson1_ComposableFunctions {
-    object Snippet1 {
-        class MainActivity : ComponentActivity() {
-            override fun onCreate(savedInstanceState: Bundle?) {
-                super.onCreate(savedInstanceState)
-                setContent {
-                    Text("Hello world!")
-                }
-            }
-        }
-    }
-    object Snippet2 {
-        class MainActivity : ComponentActivity() {
-            override fun onCreate(savedInstanceState: Bundle?) {
-                super.onCreate(savedInstanceState)
-                setContent {
-                    MessageCard("Android")
-                }
-            }
-        }
-
-        @Composable
-        fun MessageCard(name: String) {
-            Text(text = "Hello $name!")
-        }
-    }
-
-    object Snippet3 {
-        @Composable
-        fun MessageCard(name: String) {
-            Text(text = "Hello $name!")
-        }
-
-        @Preview
-        @Composable
-        fun PreviewMessageCard() {
-            MessageCard("Android")
-        }
-    }
-}
-
-private object Lesson2_Layouts {
-    object Snippet1 {
-        class MainActivity : ComponentActivity() {
-            override fun onCreate(savedInstanceState: Bundle?) {
-                super.onCreate(savedInstanceState)
-                setContent {
-                    MessageCard(Message("Android", "Jetpack Compose"))
-                }
-            }
-        }
-
-        data class Message(val author: String, val body: String)
-
-        @Composable
-        fun MessageCard(msg: Message) {
-            Text(text = msg.author)
-            Text(text = msg.body)
-        }
-
-        @Preview
-        @Composable
-        fun PreviewMessageCard() {
-            MessageCard(
-                msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
-            )
-        }
-    }
-
-    object Snippet2 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Column {
-                Text(text = msg.author)
-                Text(text = msg.body)
-            }
-        }
-    }
-
-    object Snippet3 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = "Contact profile picture",
-                )
-
-                Column {
-                    Text(text = msg.author)
-                    Text(text = msg.body)
-                }
-            }
-        }
-    }
-
-    object Snippet4 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            // Add padding around our message
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = "Contact profile picture",
-                    modifier = Modifier
-                        // Set image size to 40 dp
-                        .size(40.dp)
-                        // Clip image to be shaped as a circle
-                        .clip(CircleShape)
-                )
-
-                // Add a horizontal space between the image and the column
-                Spacer(modifier = Modifier.width(8.dp))
-
-                Column {
-                    Text(text = msg.author)
-                    // Add a vertical space between the author and message texts
-                    Spacer(modifier = Modifier.height(4.dp))
-                    Text(text = msg.body)
-                }
-            }
-        }
-    }
-}
-
-private object Lesson3_MaterialDesign {
-    object Snippet1 {
-        class MainActivity : ComponentActivity() {
-            override fun onCreate(savedInstanceState: Bundle?) {
-                super.onCreate(savedInstanceState)
-                setContent {
-                    ComposeTutorialTheme {
-                        MessageCard(Message("Android", "Jetpack Compose"))
-                    }
-                }
-            }
-        }
-
-        @Preview
-        @Composable
-        fun PreviewMessageCard() {
-            ComposeTutorialTheme {
-                MessageCard(
-                    msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
-                )
-            }
-        }
-    }
-
-    object Snippet2 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .size(40.dp)
-                        .clip(CircleShape)
-                        .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
-                )
-
-                Spacer(modifier = Modifier.width(8.dp))
-
-                Column {
-                    Text(
-                        text = msg.author,
-                        color = MaterialTheme.colors.secondaryVariant
-                    )
-
-                    Spacer(modifier = Modifier.height(4.dp))
-                    Text(text = msg.body)
-                }
-            }
-        }
-    }
-
-    object Snippet3 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .size(40.dp)
-                        .clip(CircleShape)
-                        .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
-                )
-                Spacer(modifier = Modifier.width(8.dp))
-
-                Column {
-                    Text(
-                        text = msg.author,
-                        color = MaterialTheme.colors.secondaryVariant,
-                        style = MaterialTheme.typography.subtitle2
-                    )
-
-                    Spacer(modifier = Modifier.height(4.dp))
-
-                    Text(
-                        text = msg.body,
-                        style = MaterialTheme.typography.body2
-                    )
-                }
-            }
-        }
-    }
-
-    object Snippet4 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .size(40.dp)
-                        .clip(CircleShape)
-                        .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
-                )
-                Spacer(modifier = Modifier.width(8.dp))
-
-                Column {
-                    Text(
-                        text = msg.author,
-                        color = MaterialTheme.colors.secondaryVariant,
-                        style = MaterialTheme.typography.subtitle2
-                    )
-
-                    Spacer(modifier = Modifier.height(4.dp))
-
-                    Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
-                        Text(
-                            text = msg.body,
-                            modifier = Modifier.padding(all = 4.dp),
-                            style = MaterialTheme.typography.body2
-                        )
-                    }
-                }
-            }
-        }
-
-        object Snippet5 {
-            @Preview(name = "Light Mode")
-            @Preview(
-                uiMode = Configuration.UI_MODE_NIGHT_YES,
-                showBackground = true,
-                name = "Dark Mode"
-            )
-            @Composable
-            fun PreviewMessageCard() {
-                ComposeTutorialTheme {
-                    MessageCard(
-                        msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
-                    )
-                }
-            }
-        }
-    }
-}
-
-private object Lesson4_ListsAnimations {
-    object Snippet1 {
-        // import androidx.compose.foundation.lazy.items
-
-        @Composable
-        fun Conversation(messages: List<Message>) {
-            LazyColumn {
-                items(messages) { message ->
-                    MessageCard(message)
-                }
-            }
-        }
-
-        @Preview
-        @Composable
-        fun PreviewConversation() {
-            ComposeTutorialTheme {
-                val messages = List(15) {
-                    Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
-                }
-
-                Conversation(messages)
-            }
-        }
-    }
-
-    object Snippet2 {
-        class MainActivity : ComponentActivity() {
-            override fun onCreate(savedInstanceState: Bundle?) {
-                super.onCreate(savedInstanceState)
-                setContent {
-                    ComposeTutorialTheme {
-                        val messages = List(15) {
-                            Message(
-                                "Colleague",
-                                "Hey, take a look at Jetpack Compose, it's great!\n" +
-                                    "It's the Android's modern toolkit for building native UI." +
-                                    "It simplifies and accelerates UI development on Android." +
-                                    "Quickly bring your app to life with less code, powerful " +
-                                    "tools, and intuitive Kotlin APIs"
-                            )
-                        }
-
-                        Conversation(messages)
-                    }
-                }
-            }
-        }
-
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .size(40.dp)
-                        .clip(CircleShape)
-                        .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
-                )
-                Spacer(modifier = Modifier.width(8.dp))
-
-                // We keep track if the message is expanded or not in this
-                // variable
-                var isExpanded by remember { mutableStateOf(false) }
-
-                // We toggle the isExpanded variable when we click on this Column
-                Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
-                    Text(
-                        text = msg.author,
-                        color = MaterialTheme.colors.secondaryVariant,
-                        style = MaterialTheme.typography.subtitle2
-                    )
-
-                    Spacer(modifier = Modifier.height(4.dp))
-
-                    Surface(
-                        shape = MaterialTheme.shapes.medium,
-                        elevation = 1.dp,
-                    ) {
-                        Text(
-                            text = msg.body,
-                            modifier = Modifier.padding(all = 4.dp),
-                            // If the message is expanded, we display all its content
-                            // otherwise we only display the first line
-                            maxLines = if (isExpanded) Int.MAX_VALUE else 1,
-                            style = MaterialTheme.typography.body2
-                        )
-                    }
-                }
-            }
-        }
-    }
-
-    object Snippet4 {
-        @Composable
-        fun MessageCard(msg: Message) {
-            Row(modifier = Modifier.padding(all = 8.dp)) {
-                Image(
-                    painter = painterResource(R.drawable.profile_picture),
-                    contentDescription = null,
-                    modifier = Modifier
-                        .size(40.dp)
-                        .clip(CircleShape)
-                        .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
-                )
-                Spacer(modifier = Modifier.width(8.dp))
-
-                // We keep track if the message is expanded or not in this
-                // variable
-                var isExpanded by remember { mutableStateOf(false) }
-                // surfaceColor will be updated gradually from one color to the other
-                val surfaceColor: Color by animateColorAsState(
-                    if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
-                )
-
-                // We toggle the isExpanded variable when we click on this Column
-                Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
-                    Text(
-                        text = msg.author,
-                        color = MaterialTheme.colors.secondaryVariant,
-                        style = MaterialTheme.typography.subtitle2
-                    )
-
-                    Spacer(modifier = Modifier.height(4.dp))
-
-                    Surface(
-                        shape = MaterialTheme.shapes.medium,
-                        elevation = 1.dp,
-                        // surfaceColor color will be changing gradually from primary to surface
-                        color = surfaceColor,
-                        // animateContentSize will change the Surface size gradually
-                        modifier = Modifier.animateContentSize().padding(1.dp)
-                    ) {
-                        Text(
-                            text = msg.body,
-                            modifier = Modifier.padding(all = 4.dp),
-                            // If the message is expanded, we display all its content
-                            // otherwise we only display the first line
-                            maxLines = if (isExpanded) Int.MAX_VALUE else 1,
-                            style = MaterialTheme.typography.body2
-                        )
-                    }
-                }
-            }
-        }
-    }
-}
-
-// ========================
-// Fakes below
-// ========================
-
-@Composable
-private fun ComposeTutorialTheme(content: @Composable () -> Unit) = MaterialTheme(content = content)
-
-private object R {
-    object drawable {
-        const val profile_picture = 1
-    }
-}
-
-@Repeatable
-@Retention(AnnotationRetention.SOURCE)
-private annotation class Preview(
-    val name: String = "",
-    val uiMode: Int = 0,
-    val showBackground: Boolean = true
-)
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PrimitiveInCollectionDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PrimitiveInCollectionDetector.kt
index ec98210..945f354 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PrimitiveInCollectionDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PrimitiveInCollectionDetector.kt
@@ -34,6 +34,7 @@
 import com.intellij.psi.PsiWildcardType
 import com.intellij.psi.impl.source.PsiClassReferenceType
 import java.util.EnumSet
+import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
 import org.jetbrains.kotlin.psi.KtParameter
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UField
@@ -79,6 +80,22 @@
         }
 
         override fun visitVariable(node: UVariable) {
+            // Kotlin destructuring expression is desugared. E.g.,
+            //
+            //   val (x, y) = pair
+            //
+            // is mapped to
+            //
+            //   val varHash = pair // temp variable
+            //   val x = varHash.component1()
+            //   val y = varHash.component2()
+            //
+            // and thus we don't need to analyze the temporary variable.
+            // Their `sourcePsi`s are different:
+            //   KtDestructuringDeclaration (for overall expression) v.s.
+            //   KtDestructuringDeclarationEntry (for individual local variables)
+            if (node.sourcePsi is KtDestructuringDeclaration) return
+
             val primitiveCollection = node.type.primitiveCollectionReplacement(context) ?: return
             if (node.isLambdaParameter()) {
                 // Don't notify for lambda parameters. We'll be notifying for the method
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PrimitiveInCollectionDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PrimitiveInCollectionDetectorTest.kt
index 9dc2cee..b14f040 100644
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PrimitiveInCollectionDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PrimitiveInCollectionDetectorTest.kt
@@ -414,6 +414,26 @@
         )
     }
 
+    @Test
+    fun hiddenVariableInDeconstruction() {
+        // Regression test from b/328122546
+        lint().files(
+            kotlin(
+                """
+                        package androidx.compose.lint
+
+                        fun foo(value: Any) {
+                            val list = value as List<Any>
+                            val (first, second, third) = (list as List<$type>)
+                            println(first)
+                            println(second)
+                            println(third)
+                        }
+                """
+            )
+        ).run().expectClean()
+    }
+
     data class Parameters(
         val type: String,
         val value: String,
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index ee16cb3..e06e787 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -69,6 +69,7 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
+                implementation(project(":compose:ui:ui-graphics"))
             }
         }
 
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
index 30c28cb..7bd6362 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.view.ViewGroup
-import androidx.compose.ui.R
+import androidx.compose.ui.graphics.R
 
 internal interface RippleHostKey {
     /**
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ColorSchemeDemo.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ColorSchemeDemo.kt
index 685ef98..04b2af4 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ColorSchemeDemo.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ColorSchemeDemo.kt
@@ -51,10 +51,10 @@
             Text("Surfaces", style = MaterialTheme.typography.bodyLarge)
             Spacer(modifier = Modifier.height(16.dp))
             SurfaceColorSwatch(
-                surface = colorScheme.surface,
-                surfaceText = "Surface",
-                onSurface = colorScheme.onSurface,
-                onSurfaceText = "On Surface"
+                color1 = colorScheme.surface,
+                color1Text = "Surface",
+                color2 = colorScheme.onSurface,
+                color2Text = "On Surface"
             )
             Spacer(modifier = Modifier.height(16.dp))
             DoubleTile(
@@ -85,42 +85,26 @@
                     )
                 },
             )
-            Text("Surface Container Variants", style = MaterialTheme.typography.bodyLarge)
+            Text("Surface Containers", style = MaterialTheme.typography.bodyLarge)
             Spacer(modifier = Modifier.height(16.dp))
-            DoubleTile(
-                leftTile = {
-                    ColorTile(
-                        text = "High Emphasis",
-                        color = colorScheme.surfaceContainerHigh,
-                    )
-                },
-                rightTile = {
-                    ColorTile(
-                        text = "Highest Emphasis",
-                        color = colorScheme.surfaceContainerHighest,
-                    )
-                },
+            SurfaceColorSwatch(
+                color1 = colorScheme.surfaceContainerHigh,
+                color1Text = "Surface Container High",
+                color2 = colorScheme.surfaceContainerHighest,
+                color2Text = "Surface Container Highest"
             )
-            DoubleTile(
-                leftTile = {
-                    ColorTile(
-                        text = "Low Emphasis",
-                        color = colorScheme.surfaceContainerLow,
-                    )
-                },
-                rightTile = {
-                    ColorTile(
-                        text = "Lowest Emphasis",
-                        color = colorScheme.surfaceContainerLowest,
-                    )
-                },
+            SurfaceColorSwatch(
+                color1 = colorScheme.surfaceContainerLow,
+                color1Text = "Surface Container Low",
+                color2 = colorScheme.surfaceContainerLowest,
+                color2Text = "Surface Container Lowest"
             )
             Spacer(modifier = Modifier.height(16.dp))
             SurfaceColorSwatch(
-                surface = colorScheme.surfaceVariant,
-                surfaceText = "Surface Variant",
-                onSurface = colorScheme.onSurfaceVariant,
-                onSurfaceText = "On Surface Variant"
+                color1 = colorScheme.surfaceVariant,
+                color1Text = "Surface Variant",
+                color2 = colorScheme.onSurfaceVariant,
+                color2Text = "On Surface Variant"
             )
             Spacer(modifier = Modifier.height(16.dp))
             DoubleTile(
@@ -220,18 +204,18 @@
 
 @Composable
 private fun SurfaceColorSwatch(
-    surface: Color,
-    surfaceText: String,
-    onSurface: Color,
-    onSurfaceText: String
+    color1: Color,
+    color1Text: String,
+    color2: Color,
+    color2Text: String
 ) {
     ColorTile(
-        text = surfaceText,
-        color = surface,
+        text = color1Text,
+        color = color1,
     )
     ColorTile(
-        text = onSurfaceText,
-        color = onSurface,
+        text = color2Text,
+        color = color2,
     )
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
index 9875e1b..03d0b44 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
@@ -31,8 +31,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.progressSemantics
-import androidx.compose.material3.tokens.CircularProgressIndicatorTokens
-import androidx.compose.material3.tokens.LinearProgressIndicatorTokens
+import androidx.compose.material3.tokens.ProgressIndicatorTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -856,16 +855,16 @@
     /** Default color for a linear progress indicator. */
     val linearColor: Color
         @Composable get() =
-            LinearProgressIndicatorTokens.ActiveIndicatorColor.value
+            ProgressIndicatorTokens.ActiveIndicatorColor.value
 
     /** Default color for a circular progress indicator. */
     val circularColor: Color
         @Composable get() =
-            CircularProgressIndicatorTokens.ActiveIndicatorColor.value
+            ProgressIndicatorTokens.ActiveIndicatorColor.value
 
     /** Default track color for a linear progress indicator. */
     val linearTrackColor: Color
-        @Composable get() = LinearProgressIndicatorTokens.TrackColor.value
+        @Composable get() = ProgressIndicatorTokens.TrackColor.value
 
     /** Default track color for a circular progress indicator. */
     @Deprecated(
@@ -878,14 +877,14 @@
 
     /** Default track color for a circular determinate progress indicator. */
     val circularDeterminateTrackColor: Color
-        @Composable get() = LinearProgressIndicatorTokens.TrackColor.value
+        @Composable get() = ProgressIndicatorTokens.TrackColor.value
 
     /** Default track color for a circular indeterminate progress indicator. */
     val circularIndeterminateTrackColor: Color
         @Composable get() = Color.Transparent
 
     /** Default stroke width for a circular progress indicator. */
-    val CircularStrokeWidth: Dp = CircularProgressIndicatorTokens.ActiveIndicatorWidth
+    val CircularStrokeWidth: Dp = ProgressIndicatorTokens.TrackThickness
 
     /** Default stroke cap for a linear progress indicator. */
     val LinearStrokeCap: StrokeCap = StrokeCap.Round
@@ -900,19 +899,19 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3Api
     @ExperimentalMaterial3Api
-    val LinearTrackStopIndicatorSize: Dp = 4.dp
+    val LinearTrackStopIndicatorSize: Dp = ProgressIndicatorTokens.StopSize
 
     /** Default indicator track gap size for a linear progress indicator. */
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3Api
     @ExperimentalMaterial3Api
-    val LinearIndicatorTrackGapSize: Dp = 4.dp
+    val LinearIndicatorTrackGapSize: Dp = ProgressIndicatorTokens.ActiveTrackSpace
 
     /** Default indicator track gap size for a circular progress indicator. */
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3Api
     @ExperimentalMaterial3Api
-    val CircularIndicatorTrackGapSize: Dp = 4.dp
+    val CircularIndicatorTrackGapSize: Dp = ProgressIndicatorTokens.ActiveTrackSpace
 
     /**
      * The default [AnimationSpec] that should be used when animating between progress in a
@@ -934,13 +933,13 @@
 internal val LinearIndicatorWidth = 240.dp
 
 /*@VisibleForTesting*/
-internal val LinearIndicatorHeight = LinearProgressIndicatorTokens.TrackHeight
+internal val LinearIndicatorHeight = ProgressIndicatorTokens.TrackThickness
 
 // CircularProgressIndicator Material specs
 // Diameter of the indicator circle
 /*@VisibleForTesting*/
 internal val CircularIndicatorDiameter =
-    CircularProgressIndicatorTokens.Size - CircularProgressIndicatorTokens.ActiveIndicatorWidth * 2
+    ProgressIndicatorTokens.Size - ProgressIndicatorTokens.TrackThickness * 2
 
 // Indeterminate linear indicator transition specs
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index cffb68d..935dc9e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -949,22 +949,20 @@
             return defaultSliderColorsCached ?: SliderColors(
                 thumbColor = fromToken(SliderTokens.HandleColor),
                 activeTrackColor = fromToken(SliderTokens.ActiveTrackColor),
-                activeTickColor = fromToken(SliderTokens.TickMarksActiveContainerColor)
-                    .copy(alpha = SliderTokens.TickMarksActiveContainerOpacity),
+                activeTickColor = fromToken(SliderTokens.InactiveTrackColor),
                 inactiveTrackColor = fromToken(SliderTokens.InactiveTrackColor),
-                inactiveTickColor = fromToken(SliderTokens.TickMarksInactiveContainerColor)
-                    .copy(alpha = SliderTokens.TickMarksInactiveContainerOpacity),
+                inactiveTickColor = fromToken(SliderTokens.ActiveTrackColor),
                 disabledThumbColor = fromToken(SliderTokens.DisabledHandleColor)
                     .copy(alpha = SliderTokens.DisabledHandleOpacity)
                     .compositeOver(surface),
                 disabledActiveTrackColor = fromToken(SliderTokens.DisabledActiveTrackColor)
                     .copy(alpha = SliderTokens.DisabledActiveTrackOpacity),
-                disabledActiveTickColor = fromToken(SliderTokens.TickMarksDisabledContainerColor)
-                    .copy(alpha = SliderTokens.TickMarksDisabledContainerOpacity),
+                disabledActiveTickColor = fromToken(SliderTokens.DisabledInactiveTrackColor)
+                    .copy(alpha = SliderTokens.DisabledInactiveTrackOpacity),
                 disabledInactiveTrackColor = fromToken(SliderTokens.DisabledInactiveTrackColor)
                     .copy(alpha = SliderTokens.DisabledInactiveTrackOpacity),
-                disabledInactiveTickColor = fromToken(SliderTokens.TickMarksDisabledContainerColor)
-                    .copy(alpha = SliderTokens.TickMarksDisabledContainerOpacity)
+                disabledInactiveTickColor = fromToken(SliderTokens.DisabledActiveTrackColor)
+                    .copy(alpha = SliderTokens.DisabledActiveTrackOpacity)
             ).also {
                 defaultSliderColorsCached = it
             }
@@ -1907,10 +1905,10 @@
 internal val ThumbWidth = SliderTokens.HandleWidth
 private val ThumbHeight = SliderTokens.HandleHeight
 private val ThumbSize = DpSize(ThumbWidth, ThumbHeight)
-private val TickSize = SliderTokens.TickMarksContainerSize
-private val ThumbTrackGapSize: Dp = 6.dp
+private val TickSize: Dp = 2.dp
+private val ThumbTrackGapSize: Dp = SliderTokens.ActiveHandleLeadingSpace
 private val TrackInsideCornerSize: Dp = 2.dp
-private val TrackStopIndicatorSize: Dp = 4.dp
+private val TrackStopIndicatorSize: Dp = SliderTokens.StopIndicatorSize
 private const val SliderRangeTolerance = 0.0001
 
 private enum class SliderComponents {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LinearProgressIndicatorTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LinearProgressIndicatorTokens.kt
deleted file mode 100644
index ae7c45b..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LinearProgressIndicatorTokens.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object LinearProgressIndicatorTokens {
-    val ActiveIndicatorColor = ColorSchemeKeyTokens.Primary
-    val ActiveIndicatorHeight = 4.0.dp
-    val ActiveShape = ShapeKeyTokens.CornerNone
-    val FourColorActiveIndicatorFourColor = ColorSchemeKeyTokens.TertiaryContainer
-    val FourColorActiveIndicatorOneColor = ColorSchemeKeyTokens.Primary
-    val FourColorActiveIndicatorThreeColor = ColorSchemeKeyTokens.Tertiary
-    val FourColorActiveIndicatorTwoColor = ColorSchemeKeyTokens.PrimaryContainer
-    val TrackColor = ColorSchemeKeyTokens.PrimaryContainer // TODO(b/321712387): Update tokens
-    val TrackHeight = 4.0.dp
-    val TrackShape = ShapeKeyTokens.CornerNone
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PaletteTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PaletteTokens.kt
index b4e5e3b..d802980c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PaletteTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PaletteTokens.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_162
+// VERSION: v0_210
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -36,29 +36,29 @@
     val Error95 = Color(red = 252, green = 238, blue = 238)
     val Error99 = Color(red = 255, green = 251, blue = 249)
     val Neutral0 = Color(red = 0, green = 0, blue = 0)
-    val Neutral10 = Color(red = 28, green = 27, blue = 31)
+    val Neutral10 = Color(red = 29, green = 27, blue = 32)
     val Neutral100 = Color(red = 255, green = 255, blue = 255)
-    val Neutral12 = Color(red = 32, green = 31, blue = 35)
-    val Neutral17 = Color(red = 43, green = 41, blue = 45)
-    val Neutral20 = Color(red = 49, green = 48, blue = 51)
-    val Neutral22 = Color(red = 49, green = 48, blue = 51)
-    val Neutral24 = Color(red = 49, green = 48, blue = 51)
-    val Neutral30 = Color(red = 72, green = 70, blue = 73)
-    val Neutral4 = Color(red = 14, green = 14, blue = 17)
-    val Neutral40 = Color(red = 96, green = 93, blue = 98)
-    val Neutral50 = Color(red = 120, green = 117, blue = 121)
-    val Neutral6 = Color(red = 20, green = 19, blue = 23)
-    val Neutral60 = Color(red = 147, green = 144, blue = 148)
-    val Neutral70 = Color(red = 174, green = 170, blue = 174)
-    val Neutral80 = Color(red = 201, green = 197, blue = 202)
-    val Neutral87 = Color(red = 221, green = 216, blue = 221)
-    val Neutral90 = Color(red = 230, green = 225, blue = 229)
-    val Neutral92 = Color(red = 236, green = 231, blue = 236)
-    val Neutral94 = Color(red = 241, green = 236, blue = 241)
-    val Neutral95 = Color(red = 244, green = 239, blue = 244)
-    val Neutral96 = Color(red = 247, green = 242, blue = 247)
-    val Neutral98 = Color(red = 253, green = 248, blue = 253)
-    val Neutral99 = Color(red = 255, green = 251, blue = 254)
+    val Neutral12 = Color(red = 33, green = 31, blue = 38)
+    val Neutral17 = Color(red = 43, green = 41, blue = 48)
+    val Neutral20 = Color(red = 50, green = 47, blue = 53)
+    val Neutral22 = Color(red = 54, green = 52, blue = 59)
+    val Neutral24 = Color(red = 59, green = 56, blue = 62)
+    val Neutral30 = Color(red = 72, green = 70, blue = 76)
+    val Neutral4 = Color(red = 15, green = 13, blue = 19)
+    val Neutral40 = Color(red = 96, green = 93, blue = 100)
+    val Neutral50 = Color(red = 121, green = 118, blue = 125)
+    val Neutral6 = Color(red = 20, green = 18, blue = 24)
+    val Neutral60 = Color(red = 147, green = 143, blue = 150)
+    val Neutral70 = Color(red = 174, green = 169, blue = 177)
+    val Neutral80 = Color(red = 202, green = 197, blue = 205)
+    val Neutral87 = Color(red = 222, green = 216, blue = 225)
+    val Neutral90 = Color(red = 230, green = 224, blue = 233)
+    val Neutral92 = Color(red = 236, green = 230, blue = 240)
+    val Neutral94 = Color(red = 243, green = 237, blue = 247)
+    val Neutral95 = Color(red = 245, green = 239, blue = 247)
+    val Neutral96 = Color(red = 247, green = 242, blue = 250)
+    val Neutral98 = Color(red = 254, green = 247, blue = 255)
+    val Neutral99 = Color(red = 255, green = 251, blue = 255)
     val NeutralVariant0 = Color(red = 0, green = 0, blue = 0)
     val NeutralVariant10 = Color(red = 29, green = 26, blue = 34)
     val NeutralVariant100 = Color(red = 255, green = 255, blue = 255)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/CircularProgressIndicatorTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ProgressIndicatorTokens.kt
similarity index 60%
rename from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/CircularProgressIndicatorTokens.kt
rename to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ProgressIndicatorTokens.kt
index 26701dc..ade192d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/CircularProgressIndicatorTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ProgressIndicatorTokens.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -13,20 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_103
+// Version: v2_3_5
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object CircularProgressIndicatorTokens {
+internal object ProgressIndicatorTokens {
     val ActiveIndicatorColor = ColorSchemeKeyTokens.Primary
-    val ActiveShape = ShapeKeyTokens.CornerNone
-    val ActiveIndicatorWidth = 4.0.dp
-    val FourColorActiveIndicatorFourColor = ColorSchemeKeyTokens.TertiaryContainer
-    val FourColorActiveIndicatorOneColor = ColorSchemeKeyTokens.Primary
-    val FourColorActiveIndicatorThreeColor = ColorSchemeKeyTokens.Tertiary
-    val FourColorActiveIndicatorTwoColor = ColorSchemeKeyTokens.PrimaryContainer
+    val ActiveShape = ShapeKeyTokens.CornerFull
+    val ActiveThickness = 4.0.dp
+    val ActiveTrackSpace = 4.0.dp
+    val StopColor = ColorSchemeKeyTokens.Primary
+    val StopShape = 4.0.dp
+    val StopSize = 4.0.dp
+    val TrackColor = ColorSchemeKeyTokens.SecondaryContainer
+    val TrackShape = ShapeKeyTokens.CornerFull
+    val TrackThickness = 4.0.dp
     val Size = 48.0.dp
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
index d9ecc67..607a2e8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SliderTokens.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_117
+// Version: v2_3_5
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -21,42 +21,55 @@
 import androidx.compose.ui.unit.dp
 
 internal object SliderTokens {
+    val ActiveContainerOpacity = 1.0f
+    val ActiveHandleHeight = 44.0.dp
+    val ActiveHandleLeadingSpace = 6.0.dp
+    val ActiveHandlePadding = 6.0.dp
+    val ActiveHandleShape = ShapeKeyTokens.CornerFull
+    val ActiveHandleTrailingSpace = 6.0.dp
+    val ActiveHandleWidth = 4.0.dp
     val ActiveTrackColor = ColorSchemeKeyTokens.Primary
-    val ActiveTrackHeight = 4.0.dp
+    val ActiveTrackHeight = 16.0.dp
     val ActiveTrackShape = ShapeKeyTokens.CornerFull
+    val ActiveTrackShapeLeading = ShapeKeyTokens.CornerFull
     val DisabledActiveTrackColor = ColorSchemeKeyTokens.OnSurface
-    const val DisabledActiveTrackOpacity = 0.38f
+    val DisabledActiveTrackOpacity = 0.38f
     val DisabledHandleColor = ColorSchemeKeyTokens.OnSurface
-    val DisabledHandleElevation = ElevationTokens.Level0
-    const val DisabledHandleOpacity = 0.38f
+    val DisabledHandleOpacity = 0.38f
+    val DisabledHandleWidth = 4.0.dp
     val DisabledInactiveTrackColor = ColorSchemeKeyTokens.OnSurface
-    const val DisabledInactiveTrackOpacity = 0.12f
-    val FocusHandleColor = ColorSchemeKeyTokens.Primary
+    val DisabledInactiveTrackOpacity = 0.12f
+    val DisabledStopColor = ColorSchemeKeyTokens.OnSurface
+    val FocusActiveTrackColor = ColorSchemeKeyTokens.Primary
+    val FocusHandleWidth = 2.0.dp
+    val FocusInactiveTrackColor = ColorSchemeKeyTokens.SecondaryContainer
+    val FocusStopColor = ColorSchemeKeyTokens.Primary
     val HandleColor = ColorSchemeKeyTokens.Primary
-    val HandleElevation = ElevationTokens.Level1
     val HandleHeight = 44.0.dp
     val HandleShape = ShapeKeyTokens.CornerFull
     val HandleWidth = 4.0.dp
     val HoverHandleColor = ColorSchemeKeyTokens.Primary
-    val InactiveTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+    val HoverHandleWidth = 4.0.dp
+    val HoverStopColor = ColorSchemeKeyTokens.Primary
+    val InactiveContainerOpacity = 1.0f
+    val InactiveTrackColor = ColorSchemeKeyTokens.SecondaryContainer
     val InactiveTrackHeight = 16.0.dp
     val InactiveTrackShape = ShapeKeyTokens.CornerFull
     val LabelContainerColor = ColorSchemeKeyTokens.Primary
-    val LabelContainerElevation = ElevationTokens.Level0
-    val LabelContainerHeight = 28.0.dp
-    val LabelTextColor = ColorSchemeKeyTokens.OnPrimary
-    val LabelTextFont = TypographyKeyTokens.LabelMedium
+    val LabelTextColor = ColorSchemeKeyTokens.InverseOnSurface
+    val PressedActiveTrackColor = ColorSchemeKeyTokens.Primary
     val PressedHandleColor = ColorSchemeKeyTokens.Primary
-    val StateLayerSize = 40.0.dp
-    val TrackElevation = ElevationTokens.Level0
-    val OverlapHandleOutlineColor = ColorSchemeKeyTokens.OnPrimary
-    val OverlapHandleOutlineWidth = 1.0.dp
-    val TickMarksActiveContainerColor = ColorSchemeKeyTokens.OnPrimary
-    const val TickMarksActiveContainerOpacity = 0.38f
-    val TickMarksContainerShape = ShapeKeyTokens.CornerFull
-    val TickMarksContainerSize = 2.0.dp
-    val TickMarksDisabledContainerColor = ColorSchemeKeyTokens.OnSurface
-    const val TickMarksDisabledContainerOpacity = 0.38f
-    val TickMarksInactiveContainerColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    const val TickMarksInactiveContainerOpacity = 0.38f
+    val PressedHandleWidth = 2.0.dp
+    val PressedInactiveTrackColor = ColorSchemeKeyTokens.SecondaryContainer
+    val PressedStopColor = ColorSchemeKeyTokens.Primary
+    val SliderActiveHandleColor = ColorSchemeKeyTokens.Primary
+    val StopIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
+    val StopIndicatorColorSelected = ColorSchemeKeyTokens.SecondaryContainer
+    val StopIndicatorShape = ShapeKeyTokens.CornerFull
+    val StopIndicatorSize = 4.0.dp
+    val StopIndicatorTrailingSpace = 6.0.dp
+    val ValueIndicatorActiveBottomSpace = 12.0.dp
+    val ValueIndicatorContainerColor = ColorSchemeKeyTokens.InverseSurface
+    val ValueIndicatorLabelTextColor = ColorSchemeKeyTokens.InverseOnSurface
+    val ValueIndicatorLabelTextFont = TypographyKeyTokens.LabelLarge
 }
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index a4ace77..fe7f66b 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -716,7 +716,7 @@
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(optional int capacity);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(int size, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends T> init);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf();
-    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T...? elements);
+    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T... elements);
   }
 
 }
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 339eb1e..63348ec 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -751,7 +751,7 @@
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(optional int capacity);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(int size, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends T> init);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf();
-    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T...? elements);
+    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T... elements);
   }
 
 }
diff --git a/compose/ui/ui-android-stubs/api/current.txt b/compose/ui/ui-android-stubs/api/current.txt
index 8e22228..b802776 100644
--- a/compose/ui/ui-android-stubs/api/current.txt
+++ b/compose/ui/ui-android-stubs/api/current.txt
@@ -6,6 +6,11 @@
     method public void drawRenderNode(android.view.RenderNode);
   }
 
+  public abstract class HardwareCanvas extends android.graphics.Canvas {
+    ctor public HardwareCanvas();
+    method public abstract int drawRenderNode(android.view.RenderNode, android.graphics.Rect, int);
+  }
+
   public class RenderNode {
     method public static android.view.RenderNode create(String?, android.view.View?);
     method public void destroy();
diff --git a/compose/ui/ui-android-stubs/api/restricted_current.txt b/compose/ui/ui-android-stubs/api/restricted_current.txt
index 8e22228..b802776 100644
--- a/compose/ui/ui-android-stubs/api/restricted_current.txt
+++ b/compose/ui/ui-android-stubs/api/restricted_current.txt
@@ -6,6 +6,11 @@
     method public void drawRenderNode(android.view.RenderNode);
   }
 
+  public abstract class HardwareCanvas extends android.graphics.Canvas {
+    ctor public HardwareCanvas();
+    method public abstract int drawRenderNode(android.view.RenderNode, android.graphics.Rect, int);
+  }
+
   public class RenderNode {
     method public static android.view.RenderNode create(String?, android.view.View?);
     method public void destroy();
diff --git a/compose/ui/ui-android-stubs/src/main/java/android/view/HardwareCanvas.java b/compose/ui/ui-android-stubs/src/main/java/android/view/HardwareCanvas.java
new file mode 100644
index 0000000..fae0b74
--- /dev/null
+++ b/compose/ui/ui-android-stubs/src/main/java/android/view/HardwareCanvas.java
@@ -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.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Stub for HardwareCanvas on Android L
+ */
+public abstract class HardwareCanvas extends Canvas {
+
+    /**
+     * Draws the specified display list onto this canvas.
+     *
+     * @param renderNode The RenderNode to replay.
+     * @param dirty Ignored, can be null.
+     * @param flags Optional flags about drawing, see {@link RenderNode} for
+     *              the possible flags.
+     *
+     * @return One of {@link RenderNode#STATUS_DONE} or {@link RenderNode#STATUS_DREW}
+     *         if anything was drawn.
+     */
+    public abstract int drawRenderNode(
+            @NonNull RenderNode renderNode,
+            @NonNull Rect dirty,
+            int flags
+    );
+
+    @Override
+    public void enableZ() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void disableZ() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 56a2d27..9e38779 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -73,6 +73,9 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
+                // 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")
                 api("androidx.annotation:annotation-experimental:1.4.0")
             }
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 db4ac6b..9e3fa34 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
@@ -62,9 +62,8 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-// Temporarily restrict the minSdkVersion to Android Q as the minimum API requirement will
-// be reduced in subsequent CLs
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+// Relies on View.captureToImage which is Android O+ only
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class AndroidGraphicsLayerTest {
 
     companion object {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidFloat16.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidFloat16.android.kt
deleted file mode 100644
index 5858e3d..0000000
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidFloat16.android.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.
- */
-
-package androidx.compose.ui.graphics
-
-import android.os.Build
-import android.util.Half
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
-
-// Use the platform version to benefit from ART intrinsics on API 30+
-@Suppress("NOTHING_TO_INLINE")
-internal actual inline fun floatToHalf(f: Float): Short = if (Build.VERSION.SDK_INT >= 26) {
-    Api26Impl.floatToHalf(f)
-} else {
-    softwareFloatToHalf(f)
-}
-
-// Use the platform version to benefit from ART intrinsics on API 30+
-@Suppress("NOTHING_TO_INLINE")
-internal actual inline fun halfToFloat(h: Short): Float = if (Build.VERSION.SDK_INT >= 26) {
-    Api26Impl.halfToFloat(h)
-} else {
-    softwareHalfToFloat(h)
-}
-
-@RequiresApi(26)
-internal object Api26Impl {
-    @JvmStatic
-    @DoNotInline
-    @Suppress("HalfFloat")
-    fun floatToHalf(f: Float) = Half.toHalf(f)
-
-    @JvmStatic
-    @DoNotInline
-    @Suppress("HalfFloat")
-    fun halfToFloat(h: Short) = Half.toFloat(h)
-}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
index 718543b..d9f676a 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
@@ -23,8 +23,12 @@
 import androidx.compose.ui.graphics.drawscope.DefaultDensity
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.layer.GraphicsLayerImpl
+import androidx.compose.ui.graphics.layer.GraphicsLayerV23
 import androidx.compose.ui.graphics.layer.GraphicsLayerV29
+import androidx.compose.ui.graphics.layer.GraphicsViewLayer
 import androidx.compose.ui.graphics.layer.LayerManager
+import androidx.compose.ui.graphics.layer.view.DrawChildContainer
+import androidx.compose.ui.graphics.layer.view.ViewLayerContainer
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 
@@ -41,29 +45,51 @@
 
     private val lock = Any()
     private val layerManager = LayerManager(CanvasHolder())
+    private var viewLayerContainer: DrawChildContainer? = null
 
     override fun createGraphicsLayer(): GraphicsLayer {
         synchronized(lock) {
             val ownerId = getUniqueDrawingId(ownerView)
             val layerImpl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                 GraphicsLayerV29(ownerId)
+            } else if (isRenderNodeCompatible && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                try {
+                    GraphicsLayerV23(ownerView, ownerId)
+                } catch (_: Throwable) {
+                    // If we ever failed to create an instance of the RenderNode stub based
+                    // GraphicsLayer, always fallback to creation of View based layers as it is
+                    // unlikely that subsequent attempts to create a GraphicsLayer with RenderNode
+                    // stubs would be successful.
+                    isRenderNodeCompatible = false
+                    GraphicsViewLayer(
+                        obtainViewLayerContainer(ownerView),
+                        ownerId
+                    )
+                }
             } else {
-                // Temporarily throw unsupported exceptions for API levels < Q as the GraphicsLayer
-                // implementations for lower API levels are checked in
-                throw UnsupportedOperationException(
-                    "GraphicsLayer is currently only supported on Android Q"
+                GraphicsViewLayer(
+                    obtainViewLayerContainer(ownerView),
+                    ownerId
                 )
             }
             return GraphicsLayer(layerImpl).also { layer ->
                 // Do a placeholder recording of drawing instructions to avoid errors when doing a
                 // persistence render.
                 // This will be overridden by the consumer of the created GraphicsLayer
-                layer.buildLayer(
-                    DefaultDensity,
-                    LayoutDirection.Ltr,
-                    IntSize(1, 1),
-                    GraphicsLayerImpl.DefaultDrawBlock
-                )
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P &&
+                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    // Only API levels between M (inclusive) and P (exclusive) require a placeholder
+                    // displaylist for persistence rendering. On some API levels like (ex. API 28)
+                    // actually doing a placeholder render before the activity is setup
+                    // (ex in unit tests) causes the emulator to crash with an NPE in native code
+                    // on the HWUI canvas implementation
+                    layer.buildLayer(
+                        DefaultDensity,
+                        LayoutDirection.Ltr,
+                        IntSize(1, 1),
+                        GraphicsLayerImpl.DefaultDrawBlock
+                    )
+                }
                 layerManager.persist(layer)
                 // Reset the size to zero so that immediately after GraphicsLayer creation
                 // we do not advertise a size of 1 x 1
@@ -78,6 +104,18 @@
         }
     }
 
+    private fun obtainViewLayerContainer(ownerView: ViewGroup): DrawChildContainer {
+        var container = viewLayerContainer
+        if (container == null) {
+            val context = ownerView.context
+
+            container = ViewLayerContainer(context)
+            ownerView.addView(container)
+            viewLayerContainer = container
+        }
+        return container
+    }
+
     private fun getUniqueDrawingId(view: View): Long =
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
             UniqueDrawingIdApi29.getUniqueDrawingId(view)
@@ -85,6 +123,10 @@
             -1
         }
 
+    internal companion object {
+        var isRenderNodeCompatible: Boolean = true
+    }
+
     @RequiresApi(29)
     private object UniqueDrawingIdApi29 {
         @JvmStatic
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
new file mode 100644
index 0000000..4cfe87e
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
@@ -0,0 +1,378 @@
+/*
+ * 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.graphics.layer
+
+import android.graphics.Outline
+import android.graphics.PorterDuffXfermode
+import android.os.Build
+import android.view.DisplayListCanvas
+import android.view.RenderNode
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.CanvasHolder
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.RenderEffect
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toPorterDuffMode
+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.toSize
+import java.util.concurrent.atomic.AtomicBoolean
+
+@RequiresApi(Build.VERSION_CODES.M)
+internal class GraphicsLayerV23(
+    ownerView: View,
+    override val ownerId: Long,
+    private val canvasHolder: CanvasHolder = CanvasHolder(),
+    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
+) : GraphicsLayerImpl {
+
+    private val renderNode = RenderNode.create("Compose", ownerView)
+    private var size: IntSize = IntSize.Zero
+    private var layerPaint: android.graphics.Paint? = null
+
+    private fun obtainLayerPaint(): android.graphics.Paint =
+        layerPaint ?: android.graphics.Paint().also { layerPaint = it }
+
+    init {
+        // only need to do this once
+        if (needToValidateAccess.getAndSet(false)) {
+            // This is only to force loading the DisplayListCanvas class and causing the
+            // MRenderNode to fail with a NoClassDefFoundError during construction instead of
+            // later.
+            @Suppress("UNUSED_VARIABLE")
+            val displayListCanvas: DisplayListCanvas? = null
+
+            // Ensure that we can access properties of the RenderNode. We want to force an
+            // exception here if there is a problem accessing any of these so that we can
+            // fall back to the View implementation.
+            renderNode.scaleX = renderNode.scaleX
+            renderNode.scaleY = renderNode.scaleY
+            renderNode.translationX = renderNode.translationX
+            renderNode.translationY = renderNode.translationY
+            renderNode.elevation = renderNode.elevation
+            renderNode.rotation = renderNode.rotation
+            renderNode.rotationX = renderNode.rotationX
+            renderNode.rotationY = renderNode.rotationY
+            renderNode.cameraDistance = renderNode.cameraDistance
+            renderNode.pivotX = renderNode.pivotX
+            renderNode.pivotY = renderNode.pivotY
+            renderNode.clipToOutline = renderNode.clipToOutline
+            renderNode.setClipToBounds(false)
+            renderNode.alpha = renderNode.alpha
+            renderNode.isValid // only read
+            renderNode.setLeftTopRightBottom(0, 0, 0, 0)
+            renderNode.offsetLeftAndRight(0)
+            renderNode.offsetTopAndBottom(0)
+            verifyShadowColorProperties(renderNode)
+            discardDisplayListInternal()
+            renderNode.setLayerType(View.LAYER_TYPE_NONE)
+            renderNode.setHasOverlappingRendering(renderNode.hasOverlappingRendering())
+        }
+        if (testFailCreateRenderNode) {
+            throw NoClassDefFoundError()
+        }
+
+        renderNode.setClipToBounds(false)
+        applyCompositingStrategy(CompositingStrategy.Auto)
+    }
+
+    override var compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
+        set(value) {
+            field = value
+            updateLayerProperties()
+        }
+
+    private fun applyCompositingStrategy(compositingStrategy: CompositingStrategy) {
+        renderNode.apply {
+            when (compositingStrategy) {
+                CompositingStrategy.Offscreen -> {
+                    setLayerType(View.LAYER_TYPE_HARDWARE)
+                    setLayerPaint(layerPaint)
+                    setHasOverlappingRendering(true)
+                }
+                CompositingStrategy.ModulateAlpha -> {
+                    setLayerType(View.LAYER_TYPE_NONE)
+                    setLayerPaint(layerPaint)
+                    setHasOverlappingRendering(false)
+                }
+                else -> { // CompositingStrategy.Auto
+                    setLayerType(View.LAYER_TYPE_NONE)
+                    setLayerPaint(layerPaint)
+                    setHasOverlappingRendering(true)
+                }
+            }
+        }
+    }
+
+    override var blendMode: BlendMode = BlendMode.SrcOver
+        set(value) {
+            if (field != value) {
+                field = value
+                obtainLayerPaint().apply { xfermode = PorterDuffXfermode(value.toPorterDuffMode()) }
+                updateLayerProperties()
+            }
+        }
+
+    private fun requiresCompositingLayer(): Boolean =
+        compositingStrategy == CompositingStrategy.Offscreen ||
+            blendMode != BlendMode.SrcOver ||
+            colorFilter != null
+
+    private fun updateLayerProperties() {
+        if (requiresCompositingLayer()) {
+            applyCompositingStrategy(CompositingStrategy.Offscreen)
+        } else {
+            applyCompositingStrategy(compositingStrategy)
+        }
+    }
+
+    override var colorFilter: ColorFilter? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                applyCompositingStrategy(CompositingStrategy.Offscreen)
+                renderNode.setLayerPaint(obtainLayerPaint().apply {
+                    colorFilter = value.asAndroidColorFilter()
+                })
+            } else {
+                updateLayerProperties()
+            }
+        }
+
+    override var alpha: Float = 1.0f
+        set(value) {
+            field = value
+            renderNode.setAlpha(value)
+        }
+
+    override var pivotOffset: Offset = Offset.Unspecified
+        set(value) {
+            field = value
+            renderNode.pivotX = value.x
+            renderNode.pivotY = value.y
+        }
+
+    override var scaleX: Float = 1f
+        set(value) {
+            field = value
+            renderNode.setScaleX(value)
+        }
+    override var scaleY: Float = 1f
+        set(value) {
+            field = value
+            renderNode.setScaleY(value)
+        }
+    override var translationX: Float = 0f
+        set(value) {
+            field = value
+            renderNode.setTranslationX(value)
+        }
+
+    override var translationY: Float = 1f
+        set(value) {
+            field = value
+            renderNode.setTranslationY(value)
+        }
+    override var shadowElevation: Float = 0f
+        set(value) {
+            field = value
+            renderNode.setElevation(value)
+        }
+    override var ambientShadowColor: Color = Color.Black
+        set(value) {
+            field = value
+            renderNode.setAmbientShadowColor(value.toArgb())
+        }
+    override var spotShadowColor: Color = Color.Black
+        set(value) {
+            field = value
+            renderNode.setSpotShadowColor(value.toArgb())
+        }
+    override var rotationX: Float = 0f
+        set(value) {
+            field = value
+            renderNode.setRotationX(value)
+        }
+    override var rotationY: Float = 0f
+        set(value) {
+            field = value
+            renderNode.setRotationY(value)
+        }
+    override var rotationZ: Float = 0f
+        set(value) {
+            field = value
+            renderNode.setRotation(value)
+        }
+    override var cameraDistance: Float = DefaultCameraDistance
+        set(value) {
+            // Camera distance was negated in older API levels. Maintain the same input parameters
+            // and negate the given camera distance before it is applied and also negate it when
+            // it is queried
+            field = value
+            renderNode.setCameraDistance(-value)
+        }
+
+    override var clip: Boolean = false
+        set(value) {
+            field = value
+        }
+
+    // API level 23 does not support RenderEffect so keep the field around for consistency
+    // however, it will not be applied to the rendered result. Consumers are encouraged
+    // to use the RenderEffect.isSupported API before consuming a [RenderEffect] instance.
+    // If RenderEffect is used on an unsupported API level, it should act as a no-op and not
+    // crash the compose application
+    override var renderEffect: RenderEffect? = null
+
+    override fun setPosition(topLeft: IntOffset, size: IntSize) {
+        renderNode.setLeftTopRightBottom(
+            topLeft.x,
+            topLeft.y,
+            topLeft.x + size.width,
+            topLeft.y + size.height
+        )
+        this.size = size
+    }
+
+    override fun setOutline(outline: Outline, clip: Boolean) {
+        renderNode.setOutline(outline)
+        renderNode.clipToOutline = clip
+    }
+
+    override var isInvalidated: Boolean = true
+
+    override fun buildLayer(
+        density: Density,
+        layoutDirection: LayoutDirection,
+        block: DrawScope.() -> Unit
+    ) {
+        val recordingCanvas = renderNode.start(size.width, size.height)
+        canvasHolder.drawInto(recordingCanvas) {
+            canvasDrawScope.draw(
+                density,
+                layoutDirection,
+                this,
+                size.toSize(),
+                block
+            )
+        }
+        renderNode.end(recordingCanvas)
+        isInvalidated = false
+    }
+
+    override fun draw(canvas: androidx.compose.ui.graphics.Canvas) {
+        (canvas.nativeCanvas as DisplayListCanvas).drawRenderNode(renderNode)
+    }
+
+    override fun release() {
+        discardDisplayListInternal()
+    }
+
+    override fun discardDisplayList() {
+        discardDisplayListInternal()
+    }
+
+    override val layerId: Long = 0
+
+    private fun verifyShadowColorProperties(renderNode: RenderNode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            RenderNodeVerificationHelper28.setAmbientShadowColor(
+                renderNode,
+                RenderNodeVerificationHelper28.getAmbientShadowColor(renderNode)
+            )
+            RenderNodeVerificationHelper28.setSpotShadowColor(
+                renderNode,
+                RenderNodeVerificationHelper28.getSpotShadowColor(renderNode)
+            )
+        }
+    }
+
+    private fun discardDisplayListInternal() {
+        // See b/216660268. RenderNode#discardDisplayList was originally called
+        // destroyDisplayListData on Android M and below. Make sure we gate on the corresponding
+        // API level and call the original method name on these API levels, otherwise invoke
+        // the current method name of discardDisplayList
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            RenderNodeVerificationHelper24.discardDisplayList(renderNode)
+        } else {
+            RenderNodeVerificationHelper23.destroyDisplayListData(renderNode)
+        }
+    }
+
+    companion object {
+        // Used by tests to force failing creating a RenderNode to simulate a device that
+        // doesn't support RenderNodes before Q.
+        internal var testFailCreateRenderNode = false
+
+        // We need to validate that RenderNodes can be accessed before using the RenderNode
+        // stub implementation, but we only need to validate it once. This flag indicates that
+        // validation is still needed.
+        private val needToValidateAccess = AtomicBoolean(true)
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.P)
+private object RenderNodeVerificationHelper28 {
+
+    @androidx.annotation.DoNotInline
+    fun getAmbientShadowColor(renderNode: RenderNode): Int {
+        return renderNode.ambientShadowColor
+    }
+
+    @androidx.annotation.DoNotInline
+    fun setAmbientShadowColor(renderNode: RenderNode, target: Int) {
+        renderNode.ambientShadowColor = target
+    }
+
+    @androidx.annotation.DoNotInline
+    fun getSpotShadowColor(renderNode: RenderNode): Int {
+        return renderNode.spotShadowColor
+    }
+
+    @androidx.annotation.DoNotInline
+    fun setSpotShadowColor(renderNode: RenderNode, target: Int) {
+        renderNode.spotShadowColor = target
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.N)
+private object RenderNodeVerificationHelper24 {
+
+    @androidx.annotation.DoNotInline
+    fun discardDisplayList(renderNode: RenderNode) {
+        renderNode.discardDisplayList()
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.M)
+private object RenderNodeVerificationHelper23 {
+
+    @androidx.annotation.DoNotInline
+    fun destroyDisplayListData(renderNode: RenderNode) {
+        renderNode.destroyDisplayListData()
+    }
+}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
new file mode 100644
index 0000000..aa70c09
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
@@ -0,0 +1,418 @@
+/*
+ * 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.graphics.layer
+
+import android.graphics.Canvas
+import android.graphics.Outline
+import android.graphics.PorterDuffXfermode
+import android.os.Build
+import android.view.View
+import android.view.View.LAYER_TYPE_HARDWARE
+import android.view.View.LAYER_TYPE_NONE
+import android.view.ViewOutlineProvider
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.CanvasHolder
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.RenderEffect
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.DefaultDensity
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.layer.GraphicsLayerImpl.Companion.DefaultDrawBlock
+import androidx.compose.ui.graphics.layer.view.DrawChildContainer
+import androidx.compose.ui.graphics.layer.view.PlaceholderHardwareCanvas
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toPorterDuffMode
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+
+internal class ViewLayer(
+    val ownerView: View,
+    val canvasHolder: CanvasHolder = CanvasHolder(),
+    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
+) : View(ownerView.context) {
+
+    var isInvalidated = false
+
+    init {
+        outlineProvider = LayerOutlineProvider
+    }
+
+    var layerOutline: Outline? = null
+        set(value) {
+            field = value
+            invalidateOutline()
+            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
+                // b/18175261 On the initial Lollipop release invalidateOutline
+                // would not invalidate shadows so force an invalidation here instead
+                invalidate()
+            }
+        }
+
+    internal var canUseCompositingLayer = true
+        set(value) {
+            if (field != value) {
+                field = value
+                invalidate()
+            }
+        }
+
+    private var density: Density = DefaultDensity
+    private var layoutDirection: LayoutDirection = LayoutDirection.Ltr
+    private var drawBlock: DrawScope.() -> Unit = DefaultDrawBlock
+
+    fun setDrawParams(
+        density: Density,
+        layoutDirection: LayoutDirection,
+        drawBlock: DrawScope.() -> Unit
+    ) {
+        this.density = density
+        this.layoutDirection = layoutDirection
+        this.drawBlock = drawBlock
+    }
+
+    init {
+        setWillNotDraw(false) // we WILL draw
+        this.clipBounds = null
+    }
+
+    override fun invalidate() {
+        if (!isInvalidated) {
+            isInvalidated = true
+            super.invalidate()
+        }
+    }
+
+    override fun hasOverlappingRendering(): Boolean {
+        return canUseCompositingLayer
+    }
+
+    override fun dispatchDraw(canvas: android.graphics.Canvas) {
+        canvasHolder.drawInto(canvas) {
+            canvasDrawScope.draw(
+                density,
+                layoutDirection,
+                this,
+                Size(width.toFloat(), height.toFloat()),
+                drawBlock
+            )
+        }
+        isInvalidated = false
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+    }
+
+    override fun forceLayout() {
+        // Don't do anything. These Views are treated as RenderNodes, so a forced layout
+        // should not do anything. If we keep this, we get more redrawing than is necessary.
+    }
+
+    companion object {
+        internal val LayerOutlineProvider = object : ViewOutlineProvider() {
+            override fun getOutline(view: View?, outline: Outline) {
+                if (view is ViewLayer) {
+                    view.layerOutline?.let { layerOutline ->
+                        outline.set(layerOutline)
+                    }
+                }
+            }
+        }
+    }
+}
+
+internal class GraphicsViewLayer(
+    private val layerContainer: DrawChildContainer,
+    override val ownerId: Long,
+    val canvasHolder: CanvasHolder = CanvasHolder(),
+    canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
+) : GraphicsLayerImpl {
+
+    private val viewLayer = ViewLayer(layerContainer, canvasHolder, canvasDrawScope)
+    private val resources = layerContainer.resources
+    private val clipRect = android.graphics.Rect()
+    private var layerPaint: android.graphics.Paint? = null
+
+    init {
+        layerContainer.addView(viewLayer)
+        viewLayer.clipBounds = null
+    }
+
+    private var topLeft = IntOffset.Zero
+    private var size = IntSize.Zero
+    private var clipInvalidated = false
+    override var isInvalidated: Boolean = true
+
+    override val layerId: Long = View.generateViewId().toLong()
+
+    override var blendMode: BlendMode = BlendMode.SrcOver
+        set(value) {
+            field = value
+            obtainLayerPaint().apply { xfermode = PorterDuffXfermode(value.toPorterDuffMode()) }
+            updateLayerProperties()
+        }
+    override var colorFilter: ColorFilter? = null
+        set(value) {
+            field = value
+            obtainLayerPaint().apply { this.colorFilter = value?.asAndroidColorFilter() }
+            updateLayerProperties()
+        }
+    override var compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
+        set(value) {
+            field = value
+            updateLayerProperties()
+        }
+
+    private fun applyCompositingLayer(compositingStrategy: CompositingStrategy) {
+        viewLayer.canUseCompositingLayer = when (compositingStrategy) {
+            CompositingStrategy.Offscreen -> {
+                viewLayer.setLayerType(LAYER_TYPE_HARDWARE, layerPaint)
+                true
+            }
+            CompositingStrategy.ModulateAlpha -> {
+                viewLayer.setLayerType(LAYER_TYPE_NONE, layerPaint)
+                false
+            }
+            else -> {
+                viewLayer.setLayerType(LAYER_TYPE_NONE, layerPaint)
+                true
+            }
+        }
+    }
+
+    private fun updateLayerProperties() {
+        if (requiresCompositingLayer()) {
+            applyCompositingLayer(CompositingStrategy.Offscreen)
+        } else {
+            applyCompositingLayer(compositingStrategy)
+        }
+    }
+
+    private fun obtainLayerPaint(): android.graphics.Paint =
+        layerPaint ?: android.graphics.Paint().also { layerPaint = it }
+
+    private fun requiresCompositingLayer(): Boolean =
+        compositingStrategy == CompositingStrategy.Offscreen ||
+            requiresLayerPaint()
+
+    private fun requiresLayerPaint(): Boolean =
+        blendMode != BlendMode.SrcOver || colorFilter != null
+
+    override var alpha: Float = 1f
+        set(value) {
+            field = value
+            viewLayer.setAlpha(value)
+        }
+
+    override var pivotOffset: Offset = Offset.Zero
+        set(value) {
+            field = value
+            viewLayer.pivotX = value.x
+            viewLayer.pivotY = value.y
+        }
+    override var scaleX: Float = 1f
+        set(value) {
+            field = value
+            viewLayer.scaleX = value
+        }
+    override var scaleY: Float = 1f
+        set(value) {
+            field = value
+            viewLayer.scaleY = value
+        }
+
+    override var translationX: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.translationX = value
+        }
+    override var translationY: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.translationY = value
+        }
+
+    override var shadowElevation: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.elevation = value
+        }
+    override var ambientShadowColor: Color = Color.Black
+        set(value) {
+            field = value
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                ViewLayerVerificationHelper28.setOutlineAmbientShadowColor(
+                    viewLayer,
+                    value.toArgb()
+                )
+            }
+        }
+    override var spotShadowColor: Color = Color.Black
+        set(value) {
+            field = value
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                ViewLayerVerificationHelper28.setOutlineSpotShadowColor(viewLayer, value.toArgb())
+            }
+        }
+    override var rotationX: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.rotationX = value
+        }
+    override var rotationY: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.rotationY = value
+        }
+    override var rotationZ: Float = 0f
+        set(value) {
+            field = value
+            viewLayer.rotation = value
+        }
+    override var cameraDistance: Float
+        get() {
+            return viewLayer.getCameraDistance() / resources.displayMetrics.densityDpi
+        }
+        set(value) {
+            viewLayer.setCameraDistance(value * resources.displayMetrics.densityDpi)
+        }
+    override var clip: Boolean = false
+        set(value) {
+            field = value
+            clipInvalidated = true
+        }
+    override var renderEffect: RenderEffect? = null
+        set(value) {
+            field = value
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                ViewLayerVerificationHelper31.setRenderEffect(viewLayer, value)
+            }
+        }
+
+    override fun setPosition(topLeft: IntOffset, size: IntSize) {
+        if (this.topLeft.x != topLeft.x) {
+            viewLayer.offsetLeftAndRight(topLeft.x - this.topLeft.x)
+        }
+
+        if (this.topLeft.y != topLeft.y) {
+            viewLayer.offsetTopAndBottom(topLeft.y - this.topLeft.y)
+        }
+
+        if (this.size != size) {
+            if (clip) {
+                clipInvalidated = true
+            }
+            viewLayer.layout(topLeft.x, topLeft.y, topLeft.x + size.width, topLeft.y + size.height)
+        }
+        this.topLeft = topLeft
+        this.size = size
+    }
+
+    override fun setOutline(outline: Outline, clip: Boolean) {
+        viewLayer.layerOutline = outline
+        viewLayer.clipToOutline = clip
+    }
+
+    override fun buildLayer(
+        density: Density,
+        layoutDirection: LayoutDirection,
+        block: DrawScope.() -> Unit
+    ) {
+        viewLayer.setDrawParams(density, layoutDirection, block)
+        try {
+            canvasHolder.drawInto(PlaceholderCanvas) {
+                layerContainer.drawChild(this, viewLayer, viewLayer.drawingTime)
+            }
+        } catch (t: Throwable) {
+            // We will run into class cast exceptions as View rendering attempts to
+            // cast a canvas as a DisplayListCanvas. However, this cast happens after the call to
+            // updateDisplayListIfDirty so just catch the error here and keep going
+        }
+    }
+
+    override fun draw(canvas: androidx.compose.ui.graphics.Canvas) {
+        updateClip()
+        layerContainer.drawChild(canvas, viewLayer, viewLayer.drawingTime)
+    }
+
+    private fun updateClip() {
+       if (clipInvalidated) {
+           viewLayer.clipBounds = if (clip) {
+               clipRect.apply {
+                   left = 0
+                   top = 0
+                   right = viewLayer.width
+                   bottom = viewLayer.height
+               }
+           } else {
+               null
+           }
+       }
+    }
+
+    override fun release() {
+        layerContainer.removeViewInLayout(viewLayer)
+    }
+
+    override fun discardDisplayList() {
+        release()
+    }
+
+    companion object {
+
+        val PlaceholderCanvas = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            // For Android M+ we just need a Canvas that returns true for isHardwareAccelerated
+            // in order to get the draw calls to update the displaylist of the backing View
+            object : Canvas() {
+                override fun isHardwareAccelerated(): Boolean = true
+            }
+        } else {
+            // On Android L, there is an instanceof check that verify that the Canvas is a
+            // HardwareCanvas so return our subclass of the HardwareCanvas stub
+            PlaceholderHardwareCanvas()
+        }
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.S)
+private object ViewLayerVerificationHelper31 {
+
+    @androidx.annotation.DoNotInline
+    fun setRenderEffect(view: View, target: RenderEffect?) {
+        view.setRenderEffect(target?.asAndroidRenderEffect())
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.P)
+private object ViewLayerVerificationHelper28 {
+
+    @androidx.annotation.DoNotInline
+    fun setOutlineAmbientShadowColor(view: View, target: Int) {
+        view.outlineAmbientShadowColor = target
+    }
+
+    @androidx.annotation.DoNotInline
+    fun setOutlineSpotShadowColor(view: View, target: Int) {
+        view.outlineSpotShadowColor = target
+    }
+}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/PlaceholderHardwareCanvas.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/PlaceholderHardwareCanvas.android.kt
new file mode 100644
index 0000000..a11c6b9
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/PlaceholderHardwareCanvas.android.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.graphics.layer.view
+
+import android.graphics.Rect
+import android.view.HardwareCanvas
+import android.view.RenderNode
+
+/**
+ * Implementation of HardwareCanvas abstract class used to record a displaylist
+ * on demand by passing directly to View#draw(canvas)
+ */
+internal class PlaceholderHardwareCanvas : HardwareCanvas() {
+
+    override fun drawRenderNode(renderNode: RenderNode, dirty: Rect, flags: Int): Int {
+        return 0
+    }
+
+    override fun isHardwareAccelerated(): Boolean {
+        return true
+    }
+}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt
new file mode 100644
index 0000000..23185dd
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.graphics.layer.view;
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.R
+import androidx.compose.ui.graphics.layer.ViewLayer
+import androidx.compose.ui.graphics.nativeCanvas
+
+/**
+ * The container we will use for [GraphicsViewLayer]s.
+ */
+internal class ViewLayerContainer(context: Context) : DrawChildContainer(context) {
+    override fun dispatchDraw(canvas: android.graphics.Canvas) {
+        // we draw our children as part of AndroidComposeView.dispatchDraw
+    }
+
+    /**
+     * We control our own child Views and we don't want the View system to force updating
+     * the display lists.
+     * We override hidden protected method from ViewGroup
+     */
+    protected fun dispatchGetDisplayList() {
+    }
+}
+
+/**
+ * The container we will use for [ViewLayer]s when [ViewLayer.shouldUseDispatchDraw] is true.
+ */
+internal open class DrawChildContainer(context: Context) : ViewGroup(context) {
+    private var isDrawing = false
+
+    init {
+        clipChildren = false
+        clipToPadding = false
+
+        // Hide this view and its children in tools:
+        setTag(R.id.hide_in_inspector_tag, true)
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        // we don't layout our children
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        // we don't measure our children
+        setMeasuredDimension(0, 0)
+    }
+
+    override fun dispatchDraw(canvas: android.graphics.Canvas) {
+        // We must updateDisplayListIfDirty for all invalidated Views.
+
+        // We only want to call super.dispatchDraw() if there is an invalidated layer
+        var doDispatch = false
+        for (i in 0 until super.getChildCount()) {
+            val child = getChildAt(i) as ViewLayer
+            if (child.isInvalidated) {
+                doDispatch = true
+                break
+            }
+        }
+
+        if (doDispatch) {
+            isDrawing = true
+            try {
+                super.dispatchDraw(canvas)
+            } finally {
+                isDrawing = false
+            }
+        }
+    }
+
+    /**
+     * We don't want to advertise children to the transition system. ViewLayers shouldn't be
+     * watched for add/remove for transitions purposes.
+     */
+    override fun getChildCount(): Int = if (isDrawing) super.getChildCount() else 0
+
+    // we change visibility for this method so ViewLayer can use it for drawing
+    internal fun drawChild(canvas: Canvas, view: View, drawingTime: Long) {
+        super.drawChild(canvas.nativeCanvas, view, drawingTime)
+    }
+}
diff --git a/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml b/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml
new file mode 100644
index 0000000..989568a
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <item name="hide_in_inspector_tag" type="id" />
+</resources>
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
index 27b2ece..d04cfcd 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
@@ -27,16 +27,18 @@
  *
  * The IEEE 754 standard specifies an fp16 as having the following format:
  *
- *  * Sign bit: 1 bit
- *  * Exponent width: 5 bits
- *  * Significand: 10 bits
+ *  - Sign bit: 1 bit
+ *  - Exponent width: 5 bits
+ *  - Significand: 10 bits
  *
  * The format is laid out as follows:
+ * ```
  *     1   11111   1111111111
  *     ^   --^--   -----^----
  *     sign  |          |_______ significand
  *     |
  *     -- exponent
+ * ```
  *
  * Half-precision floating points can be useful to save memory and/or
  * bandwidth at the expense of range and precision when compared to single-precision
@@ -49,31 +51,31 @@
  *
  * <table summary="Precision of fp16 across the range">
  * <tr><th>Range start</th><th>Precision</th></tr>
- * <tr><td>0</td><td>1  16,777,216</td></tr>
- * <tr><td>1  16,384</td><td>1  16,777,216</td></tr>
- * <tr><td>1  8,192</td><td>1  8,388,608</td></tr>
- * <tr><td>1  4,096</td><td>1  4,194,304</td></tr>
- * <tr><td>1  2,048</td><td>1  2,097,152</td></tr>
- * <tr><td>1  1,024</td><td>1  1,048,576</td></tr>
- * <tr><td>1  512</td><td>1  524,288</td></tr>
- * <tr><td>1  256</td><td>1  262,144</td></tr>
- * <tr><td>1  128</td><td>1  131,072</td></tr>
- * <tr><td>1  64</td><td>1  65,536</td></tr>
- * <tr><td>1  32</td><td>1  32,768</td></tr>
- * <tr><td>1  16</td><td>1  16,384</td></tr>
- * <tr><td>1  8</td><td>1  8,192</td></tr>
- * <tr><td>1  4</td><td>1  4,096</td></tr>
- * <tr><td>1  2</td><td>1  2,048</td></tr>
- * <tr><td>1</td><td>1  1,024</td></tr>
- * <tr><td>2</td><td>1  512</td></tr>
- * <tr><td>4</td><td>1  256</td></tr>
- * <tr><td>8</td><td>1  128</td></tr>
- * <tr><td>16</td><td>1  64</td></tr>
- * <tr><td>32</td><td>1  32</td></tr>
- * <tr><td>64</td><td>1  16</td></tr>
- * <tr><td>128</td><td>1  8</td></tr>
- * <tr><td>256</td><td>1  4</td></tr>
- * <tr><td>512</td><td>1  2</td></tr>
+ * <tr><td>0</td><td>1/16,777,216</td></tr>
+ * <tr><td>1/16,384</td><td>1/16,777,216</td></tr>
+ * <tr><td>1/8,192</td><td>1/8,388,608</td></tr>
+ * <tr><td>1/4,096</td><td>1/4,194,304</td></tr>
+ * <tr><td>1/2,048</td><td>1/2,097,152</td></tr>
+ * <tr><td>1/1,024</td><td>1/1,048,576</td></tr>
+ * <tr><td>1/512</td><td>1/524,288</td></tr>
+ * <tr><td>1/256</td><td>1/262,144</td></tr>
+ * <tr><td>1/128</td><td>1/131,072</td></tr>
+ * <tr><td>1/64</td><td>1/65,536</td></tr>
+ * <tr><td>1/32</td><td>1/32,768</td></tr>
+ * <tr><td>1/16</td><td>1/16,384</td></tr>
+ * <tr><td>1/8</td><td>1/8,192</td></tr>
+ * <tr><td>1/4</td><td>1/4,096</td></tr>
+ * <tr><td>1/2</td><td>1/2,048</td></tr>
+ * <tr><td>1</td><td>1/1,024</td></tr>
+ * <tr><td>2</td><td>1/512</td></tr>
+ * <tr><td>4</td><td>1/256</td></tr>
+ * <tr><td>8</td><td>1/128</td></tr>
+ * <tr><td>16</td><td>1/64</td></tr>
+ * <tr><td>32</td><td>1/32</td></tr>
+ * <tr><td>64</td><td>1/16</td></tr>
+ * <tr><td>128</td><td>1/8</td></tr>
+ * <tr><td>256</td><td>1/4</td></tr>
+ * <tr><td>512</td><td>1/2</td></tr>
  * <tr><td>1,024</td><td>1</td></tr>
  * <tr><td>2,048</td><td>2</td></tr>
  * <tr><td>4,096</td><td>4</td></tr>
@@ -82,12 +84,10 @@
  * <tr><td>32,768</td><td>32</td></tr>
  * </table>
  *
- *
  * This table shows that numbers higher than 1024 lose all fractional precision.
  */
 @JvmInline
 internal value class Float16(val halfValue: Short) : Comparable<Float16> {
-
     /**
      * Constructs a newly allocated `Float16` object that represents the
      * argument converted to a half-precision float.
@@ -599,14 +599,11 @@
  * Convert a single-precision float to a half-precision float, stored as
  * [Short] data type to hold its 16 bits.
  */
-internal expect fun floatToHalf(f: Float): Short
-
-// Provided here as a convenience for `actual` implementations
 @Suppress("NOTHING_TO_INLINE")
-internal inline fun softwareFloatToHalf(f: Float): Short {
+internal inline fun floatToHalf(f: Float): Short {
     val bits = f.toRawBits()
-    val s = bits.ushr(Fp32SignShift)
-    var e = bits.ushr(Fp32ExponentShift) and Fp32ExponentMask
+    val s = bits ushr Fp32SignShift
+    var e = bits ushr Fp32ExponentShift and Fp32ExponentMask
     var m = bits and Fp32SignificandMask
 
     var outE = 0
@@ -647,14 +644,11 @@
 /**
  * Convert a half-precision float to a single-precision float.
  */
-internal expect fun halfToFloat(h: Short): Float
-
-// Provided here as a convenience for `actual` implementations
 @Suppress("NOTHING_TO_INLINE")
-internal inline fun softwareHalfToFloat(h: Short): Float {
+internal inline fun halfToFloat(h: Short): Float {
     val bits = h.toInt() and 0xffff
     val s = bits and Fp16SignMask
-    val e = bits.ushr(Fp16ExponentShift) and Fp16ExponentMask
+    val e = bits ushr Fp16ExponentShift and Fp16ExponentMask
     val m = bits and Fp16SignificandMask
 
     var outE = 0
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopFloat16.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopFloat16.desktop.kt
deleted file mode 100644
index c102496..0000000
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopFloat16.desktop.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-
-package androidx.compose.ui.graphics
-
-internal actual fun floatToHalf(f: Float): Short = softwareFloatToHalf(f)
-
-internal actual fun halfToFloat(h: Short): Float = softwareHalfToFloat(h)
diff --git a/compose/ui/ui-inspection/build.gradle b/compose/ui/ui-inspection/build.gradle
index ecf1f1c..5f0952ee 100644
--- a/compose/ui/ui-inspection/build.gradle
+++ b/compose/ui/ui-inspection/build.gradle
@@ -42,6 +42,7 @@
     compileOnly(libs.kotlinStdlib)
     compileOnly("androidx.inspection:inspection:1.0.0")
     compileOnly("androidx.compose.runtime:runtime:1.2.1")
+    compileOnly(project(":compose:ui:ui-graphics"))
     compileOnly(project(":compose:ui:ui"))
     // we ignore its transitive dependencies, because ui-inspection should
     // depend on them as "compile-only" deps.
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 85f5c4b..0140b6d 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -21,7 +21,7 @@
 import android.view.ViewGroup
 import androidx.collection.LongList
 import androidx.collection.mutableLongListOf
-import androidx.compose.ui.R
+import androidx.compose.ui.graphics.R
 import androidx.compose.ui.inspection.framework.ancestors
 import androidx.compose.ui.inspection.framework.getChildren
 import androidx.compose.ui.inspection.framework.isAndroidComposeView
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 5266e91..5a5f447 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -715,7 +715,7 @@
   }
 
   public static interface AndroidFont.TypefaceLoader {
-    method public suspend Object? awaitLoad(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font, kotlin.coroutines.Continuation<? super android.graphics.Typeface>);
+    method public suspend Object? awaitLoad(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font, kotlin.coroutines.Continuation<? super android.graphics.Typeface?>);
     method public android.graphics.Typeface? loadBlocking(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font);
   }
 
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 07aa3d7..272f48a 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -715,7 +715,7 @@
   }
 
   public static interface AndroidFont.TypefaceLoader {
-    method public suspend Object? awaitLoad(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font, kotlin.coroutines.Continuation<? super android.graphics.Typeface>);
+    method public suspend Object? awaitLoad(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font, kotlin.coroutines.Continuation<? super android.graphics.Typeface?>);
     method public android.graphics.Typeface? loadBlocking(android.content.Context context, androidx.compose.ui.text.font.AndroidFont font);
   }
 
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index e0e50d7..67395d6 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -71,7 +71,7 @@
                 api("androidx.annotation:annotation:1.1.0")
                 implementation(project(":compose:animation:animation"))
                 implementation("androidx.savedstate:savedstate-ktx:1.2.1")
-                implementation("androidx.compose.material:material:1.0.0")
+                implementation(project(":compose:material:material"))
                 implementation("androidx.activity:activity-compose:1.7.0")
                 implementation("androidx.lifecycle:lifecycle-common:2.6.1")
 
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 530e8cf..0e97748 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -70,13 +70,22 @@
   @androidx.compose.runtime.Immutable public final class BiasAbsoluteAlignment implements androidx.compose.ui.Alignment {
     ctor public BiasAbsoluteAlignment(float horizontalBias, float verticalBias);
     method public long align(long size, long space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
+    method public float component2();
     method public androidx.compose.ui.BiasAbsoluteAlignment copy(float horizontalBias, float verticalBias);
+    method public float getHorizontalBias();
+    method public float getVerticalBias();
+    property public final float horizontalBias;
+    property public final float verticalBias;
   }
 
   @androidx.compose.runtime.Immutable public static final class BiasAbsoluteAlignment.Horizontal implements androidx.compose.ui.Alignment.Horizontal {
     ctor public BiasAbsoluteAlignment.Horizontal(float bias);
     method public int align(int size, int space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
     method public androidx.compose.ui.BiasAbsoluteAlignment.Horizontal copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   @androidx.compose.runtime.Immutable public final class BiasAlignment implements androidx.compose.ui.Alignment {
@@ -94,13 +103,19 @@
   @androidx.compose.runtime.Immutable public static final class BiasAlignment.Horizontal implements androidx.compose.ui.Alignment.Horizontal {
     ctor public BiasAlignment.Horizontal(float bias);
     method public int align(int size, int space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
     method public androidx.compose.ui.BiasAlignment.Horizontal copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   @androidx.compose.runtime.Immutable public static final class BiasAlignment.Vertical implements androidx.compose.ui.Alignment.Vertical {
     ctor public BiasAlignment.Vertical(float bias);
     method public int align(int size, int space);
+    method public float component1();
     method public androidx.compose.ui.BiasAlignment.Vertical copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   public final class CombinedModifier implements androidx.compose.ui.Modifier {
@@ -1708,7 +1723,7 @@
     method public long getSize();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
     method public default suspend <T> Object? withTimeout(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
-    method public default suspend <T> Object? withTimeoutOrNull(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+    method public default suspend <T> Object? withTimeoutOrNull(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T?>);
     property public abstract androidx.compose.ui.input.pointer.PointerEvent currentEvent;
     property public default long extendedTouchPadding;
     property public abstract long size;
@@ -1960,10 +1975,10 @@
   }
 
   public sealed interface SuspendingPointerInputModifierNode extends androidx.compose.ui.node.PointerInputModifierNode {
-    method public kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object> getPointerInputHandler();
+    method public kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object?> getPointerInputHandler();
     method public void resetPointerInputHandler();
     method public void setPointerInputHandler(kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>);
-    property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object> pointerInputHandler;
+    property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object?> pointerInputHandler;
   }
 
 }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 394a8532..54d037a 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -70,13 +70,22 @@
   @androidx.compose.runtime.Immutable public final class BiasAbsoluteAlignment implements androidx.compose.ui.Alignment {
     ctor public BiasAbsoluteAlignment(float horizontalBias, float verticalBias);
     method public long align(long size, long space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
+    method public float component2();
     method public androidx.compose.ui.BiasAbsoluteAlignment copy(float horizontalBias, float verticalBias);
+    method public float getHorizontalBias();
+    method public float getVerticalBias();
+    property public final float horizontalBias;
+    property public final float verticalBias;
   }
 
   @androidx.compose.runtime.Immutable public static final class BiasAbsoluteAlignment.Horizontal implements androidx.compose.ui.Alignment.Horizontal {
     ctor public BiasAbsoluteAlignment.Horizontal(float bias);
     method public int align(int size, int space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
     method public androidx.compose.ui.BiasAbsoluteAlignment.Horizontal copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   @androidx.compose.runtime.Immutable public final class BiasAlignment implements androidx.compose.ui.Alignment {
@@ -94,13 +103,19 @@
   @androidx.compose.runtime.Immutable public static final class BiasAlignment.Horizontal implements androidx.compose.ui.Alignment.Horizontal {
     ctor public BiasAlignment.Horizontal(float bias);
     method public int align(int size, int space, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+    method public float component1();
     method public androidx.compose.ui.BiasAlignment.Horizontal copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   @androidx.compose.runtime.Immutable public static final class BiasAlignment.Vertical implements androidx.compose.ui.Alignment.Vertical {
     ctor public BiasAlignment.Vertical(float bias);
     method public int align(int size, int space);
+    method public float component1();
     method public androidx.compose.ui.BiasAlignment.Vertical copy(float bias);
+    method public float getBias();
+    property public final float bias;
   }
 
   public final class CombinedModifier implements androidx.compose.ui.Modifier {
@@ -1708,7 +1723,7 @@
     method public long getSize();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
     method public default suspend <T> Object? withTimeout(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
-    method public default suspend <T> Object? withTimeoutOrNull(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+    method public default suspend <T> Object? withTimeoutOrNull(long timeMillis, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T?>);
     property public abstract androidx.compose.ui.input.pointer.PointerEvent currentEvent;
     property public default long extendedTouchPadding;
     property public abstract long size;
@@ -1960,10 +1975,10 @@
   }
 
   public sealed interface SuspendingPointerInputModifierNode extends androidx.compose.ui.node.PointerInputModifierNode {
-    method public kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object> getPointerInputHandler();
+    method public kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object?> getPointerInputHandler();
     method public void resetPointerInputHandler();
     method public void setPointerInputHandler(kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>);
-    property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object> pointerInputHandler;
+    property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.input.pointer.PointerInputScope,kotlin.coroutines.Continuation<? super kotlin.Unit>,java.lang.Object?> pointerInputHandler;
   }
 
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
index 2314312..b591a0e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
@@ -109,10 +109,7 @@
         isDebugInspectorInfoEnabled = false
     }
 
-    // Temporarily restrict test to Android Q+ as minimum API requirements are loosened
-    // with support for lower API levels in subsequent CLs
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testRememberGraphicsLayerReleasedAfterComposableDisposed() {
         var graphicsLayer: GraphicsLayer? = null
         val useGraphicsLayerComposable = mutableStateOf(true)
@@ -133,10 +130,7 @@
         assertTrue(graphicsLayer!!.isReleased)
     }
 
-    // Temporarily restrict test to Android Q+ as minimum API requirements are loosened
-    // with support for lower API levels in subsequent CLs
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testObtainGraphicsLayerReleasedAfterModifierDetached() {
         var graphicsLayer: GraphicsLayer? = null
         val useCacheModifier = mutableStateOf(true)
@@ -168,10 +162,8 @@
         assertTrue(graphicsLayer!!.isReleased)
     }
 
-    // Temporarily restrict test to Android Q+ as minimum API requirements are loosened
-    // with support for lower API levels in subsequent CLs
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testBuildLayerWithCache() {
         var graphicsLayer: GraphicsLayer? = null
         val testTag = "TestTag"
@@ -218,10 +210,8 @@
         }
     }
 
-    // Temporarily restrict test to Android Q+ as minimum API requirements are loosened
-    // with support for lower API levels in subsequent CLs
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testGraphicsLayerPersistence() {
         val testTag = "TestTag"
         val drawGraphicsLayer = mutableStateOf(0)
@@ -279,10 +269,8 @@
         rule.onNodeWithTag(testTag).captureToImage().toPixelMap().apply { verifyColor(rectColor) }
     }
 
-    // Temporarily restrict test to Android Q+ as minimum API requirements are loosened
-    // with support for lower API levels in subsequent CLs
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testBuildLayerDrawContent() {
         val testTag = "TestTag"
         val targetColor = Color.Blue
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 812dabd..2baedd8 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.graphics.vector
 
+import android.app.Activity
 import android.app.Application
 import android.content.ComponentCallbacks2
 import android.content.pm.ActivityInfo
@@ -23,6 +24,7 @@
 import android.content.res.Resources
 import android.graphics.Bitmap
 import android.os.Build
+import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.annotation.RequiresApi
 import androidx.compose.foundation.Image
@@ -1154,17 +1156,13 @@
         assertTrue("Cache was not cleared after trim memory call", cacheCleared)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testImageVectorConfigChange() {
-        val tag = "testTag"
-        rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-
-        val latch = CountDownLatch(1)
-
-        rule.activity.application.registerComponentCallbacks(object : ComponentCallbacks2 {
+    private fun Activity.rotate(rotation: Int): Boolean {
+        var rotationCount = 0
+        var rotateSuccess = false
+        var latch: CountDownLatch? = null
+        val callbacks = object : ComponentCallbacks2 {
             override fun onConfigurationChanged(p0: Configuration) {
-                latch.countDown()
+                latch?.countDown()
             }
 
             override fun onLowMemory() {
@@ -1174,10 +1172,31 @@
             override fun onTrimMemory(p0: Int) {
                 // NO-OP
             }
-        })
-
+        }
+        application.registerComponentCallbacks(callbacks)
         try {
-            latch.await(1500, TimeUnit.MILLISECONDS)
+            while (rotationCount < 3 && !rotateSuccess) {
+                latch = CountDownLatch(1)
+                this.requestedOrientation = rotation
+                rotateSuccess = latch.await(3000, TimeUnit.MILLISECONDS) &&
+                    this.requestedOrientation == rotation
+                rotationCount++
+            }
+        } finally {
+            application.unregisterComponentCallbacks(callbacks)
+        }
+        return rotateSuccess
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testImageVectorConfigChange() {
+        if (!rule.activity.rotate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
+            Log.w(TAG, "device rotation unsuccessful")
+            return
+        }
+        val tag = "testTag"
+        try {
             rule.setContent {
                 Image(
                     painterResource(R.drawable.ic_triangle_config),
@@ -1191,7 +1210,7 @@
         } catch (e: InterruptedException) {
             fail("Unable to verify vector asset in landscape orientation")
         } finally {
-            rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+            rule.activity.rotate(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
         }
     }
 
@@ -1458,4 +1477,6 @@
         Assert.assertEquals(height, bitmap.height)
         return bitmap
     }
+
+    private val TAG = "VectorTest"
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
index 6716cc4..118695c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
@@ -19,8 +19,8 @@
 import android.content.Context
 import android.view.View
 import android.view.ViewGroup
-import androidx.compose.ui.R
 import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.R
 import androidx.compose.ui.graphics.nativeCanvas
 
 /**
diff --git a/compose/ui/ui/src/androidMain/res/values/ids.xml b/compose/ui/ui/src/androidMain/res/values/ids.xml
index 721b41d..140ecaf 100644
--- a/compose/ui/ui/src/androidMain/res/values/ids.xml
+++ b/compose/ui/ui/src/androidMain/res/values/ids.xml
@@ -52,6 +52,5 @@
     <item name="inspection_slot_table_set" type="id" />
     <item name="androidx_compose_ui_view_composition_context" type="id" />
     <item name="compose_view_saveable_id_tag" type="id" />
-    <item name="hide_in_inspector_tag" type="id" />
     <item name="consume_window_insets_tag" type="id" />
 </resources>
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Alignment.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Alignment.kt
index 94e2368..1226144 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Alignment.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Alignment.kt
@@ -185,7 +185,7 @@
      * @see Vertical
      */
     @Immutable
-    data class Horizontal(private val bias: Float) : Alignment.Horizontal {
+    data class Horizontal(val bias: Float) : Alignment.Horizontal {
         override fun align(size: Int, space: Int, layoutDirection: LayoutDirection): Int {
             // Convert to Px first and only round at the end, to avoid rounding twice while
             // calculating the new positions
@@ -205,7 +205,7 @@
      * @see Horizontal
      */
     @Immutable
-    data class Vertical(private val bias: Float) : Alignment.Vertical {
+    data class Vertical(val bias: Float) : Alignment.Vertical {
         override fun align(size: Int, space: Int): Int {
             // Convert to Px first and only round at the end, to avoid rounding twice while
             // calculating the new positions
@@ -227,8 +227,8 @@
  */
 @Immutable
 data class BiasAbsoluteAlignment(
-    private val horizontalBias: Float,
-    private val verticalBias: Float
+    val horizontalBias: Float,
+    val verticalBias: Float
 ) : Alignment {
     /**
      * Returns the position of a 2D point in a container of a given size, according to this
@@ -256,7 +256,7 @@
      * @see BiasAlignment.Horizontal
      */
     @Immutable
-    data class Horizontal(private val bias: Float) : Alignment.Horizontal {
+    data class Horizontal(val bias: Float) : Alignment.Horizontal {
         /**
          * Returns the position of a 2D point in a container of a given size,
          * according to this [BiasAbsoluteAlignment.Horizontal]. This position will not be
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
index 34f9ef3..9b2fa6f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
@@ -49,7 +49,8 @@
 
 /**
  * Use this function to observe snapshot reads for any target within the specified [block].
- * [onDrawCacheReadsChanged] is called when any of the observed values within the snapshot change.
+ * [ObserverModifierNode.onObservedReadsChanged] is called when any of the observed values within
+ * the snapshot change.
  */
 fun <T> T.observeReads(block: () -> Unit) where T : Modifier.Node, T : ObserverModifierNode {
     val target = ownerScope ?: ObserverNodeOwnerScope(this).also { ownerScope = it }
diff --git a/core/core/src/main/java/androidx/core/DeleteMe.kt b/core/core/src/main/java/androidx/core/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/core/core/src/main/java/androidx/core/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/development/bench-flame-diff/.gitignore b/development/bench-flame-diff/.gitignore
new file mode 100644
index 0000000..9324f7a
--- /dev/null
+++ b/development/bench-flame-diff/.gitignore
@@ -0,0 +1,22 @@
+# Ignore Gradle project-specific cache directory
+.gradle
+
+# Ignore Gradle build output directory
+build
+
+# Ignore dependencies
+.deps
+
+# Ignore saved traces
+saved-traces
+
+# Ignore saved diffs
+saved-diffs
+
+# Ignore archived
+archive/*
+
+# Ignore completion files
+completion_bash.sh
+completion_fish.sh
+completion_zsh.sh
diff --git a/development/bench-flame-diff/README.md b/development/bench-flame-diff/README.md
new file mode 100644
index 0000000..87977c0
--- /dev/null
+++ b/development/bench-flame-diff/README.md
@@ -0,0 +1,77 @@
+# Overview
+
+The project provides an easy way to save before/after CPU traces from Microbenchmark runs, and compare them visually using Differential Flame Graphs.
+
+![Differential graph illustration](assets/illustration-diff.webp)
+
+Areas where the code got slower are highlighted in red, while areas that are now faster are marked in blue; the intensity of the colour is proportional to the size of the difference.
+
+See also the [end-to-end demo (video)](https://drive.google.com/file/d/119nI_zlAMbTHzh-Rdzf8UuUVCGEKnKFQ/view?usp=drive_link&resourcekey=0-SRRmKgVZYfAlnkL4Hvh-cg).
+
+# Usage
+
+## Interacting with the script
+
+- Overview of all commands: `./bench-flame-diff.sh -h`
+- Help for a specific command: `./bench-flame-diff.sh <command> -h`
+
+## First usage
+
+On first usage, initialise all dependencies by running: `./bench-flame-diff.sh init`
+
+## General workflow
+
+1. Run a specific Microbenchmark with CPU Stack sampling enabled (see below for instructions)
+1. Save the trace as _base_ for comparison using `./bench-flame-diff.sh save`. It's worth picking a good names for the saved traces since you're likely going to e.g. re-use the _base_ while iterating on code changes.
+1. Apply changes in your code and run the same benchmark as in step 1
+1. Save the trace as _current_ `./bench-flame-diff.sh save`
+1. Compare both traces using `./bench-flame-diff.sh diff` which will create and open a diff in a web browser
+1. Toggle between graphs using the buttons on the top:
+   - `base`: flamegraph for the _base_ trace
+   - `base-vs-curr`: differential flame graph showing _base_ vs _current_ on the _base_ trace
+   - `curr`: flamegraph for the _current_ trace
+   - `curr-vs-base`: differential flame graph showing _base_ vs _current_ on the _current_ trace
+1. You can later go back to generated diffs using `./bench-flame-diff.sh open`
+
+# Misc
+
+## Enabling stack sampling in Benchmark traces
+
+This can be done in CLI or by editing `build.gradle`. Full documentation is [here](https://developer.android.com/topic/performance/benchmarking/microbenchmark-profile).
+
+Quick CLI example:
+```
+# pick a target benchmark
+tgt=:compose:foundation:foundation-benchmark:connectedCheck
+
+# create a regex that targets a specific benchmark (test)
+test_rx="androidx.compose.foundation.benchmark.lazy.LazyListScrollingBenchmark.scrollProgrammatically_noNewItems\[.*Row.*\]"
+
+# run the benchmark and gather a 5 second (default) stack sample at 1000 Hz (default)
+./gradlew $tgt -Pandroid.testInstrumentationRunnerArguments.tests_regex="$test_rx" \
+ -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.mode=StackSampling \
+ -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.sampleDurationSeconds=5 \
+ -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.sampleFrequency=1000
+```
+
+## CLI completion
+
+Generate completion files with `./generate-completion-files.sh` and source in your shell config, e.g.:
+- For `bash`: `dst="$(pwd)/completion_bash.sh"; echo "source '$dst'" >> ~/.bashrc`
+- For `zsh`: `dst="$(pwd)/completion_zsh.sh"; echo "source '$dst'" >> ~/.zshrc`
+
+After restarting the shell session, you will be able to 'tab-autocomplete' commands and argument names.
+
+# Dependencies
+
+On top of dependencies discoverable with `./gradlew app:dependencies` the project depends on:
+- https://github.com/brendangregg/FlameGraph
+- https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/scripts
+
+Both are fetched from the network in the `init` command and pinned to known-good-revisions.
+
+# Reporting issues
+
+File an issue on Buganizer using [this link](https://b.corp.google.com/issues/new?component=1229612&hotlistIds=3622386&hotlistIds=5709693&assignee=jgielzak@google.com&title=bench-flame-diff:%20) or reach out directly to [jgielzak@](http://go/moma/chat?with=jgielzak).
+
+Known issues and future work items are tracked [here](https://b.corp.google.com/hotlists/5709693).
diff --git a/development/bench-flame-diff/app/build.gradle.kts b/development/bench-flame-diff/app/build.gradle.kts
new file mode 100644
index 0000000..39efc0b
--- /dev/null
+++ b/development/bench-flame-diff/app/build.gradle.kts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+plugins {
+    alias(libs.plugins.jvm)
+    application
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation("com.github.ajalt.clikt:clikt:4.2.1")
+    implementation("com.zaxxer:nuprocess:2.0.6")
+}
+
+java {
+    toolchain {
+        languageVersion.set(JavaLanguageVersion.of(11))
+    }
+}
+
+application {
+    mainClass.set("bench.flame.diff.AppKt")
+    applicationName = "bench-flame-diff"
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/App.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/App.kt
new file mode 100644
index 0000000..2d45e58
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/App.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 bench.flame.diff
+
+import bench.flame.diff.command.Diff
+import bench.flame.diff.command.Init
+import bench.flame.diff.command.List
+import bench.flame.diff.command.Open
+import bench.flame.diff.command.Save
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.context
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.output.MordantHelpFormatter
+import com.github.ajalt.mordant.rendering.Widget
+import com.github.ajalt.mordant.table.horizontalLayout
+import com.github.ajalt.mordant.table.verticalLayout
+import com.github.ajalt.mordant.widgets.Text
+import com.github.ajalt.mordant.widgets.withPadding
+
+fun main(args: Array<out String>) {
+    BenchFlameDiff().subcommands(Init(), Save(), List(), Diff(), Open()).main(args)
+}
+
+class BenchFlameDiff : CliktCommand(
+    help = "Generate differential flame graphs from microbenchmark CPU traces.",
+    epilog = """
+        Dependencies
+        FlameGraph - https://github.com/brendangregg/FlameGraph for generating graphs.
+        Simpleperf - Android NDK's Simpleperf for converting traces into FlameGraph input format.
+        Clikt      - com.github.ajalt.clikt:clikt for the CLI interface.
+        NuProcess  - com.zaxxer:nuprocess for running external processes.
+    """.trimIndent()
+) {
+    init {
+        context {
+            helpFormatter = {
+                object : MordantHelpFormatter(it, showRequiredTag = true) {
+                    override fun renderEpilog(epilog: String): Widget = verticalLayout {
+                        val lines = epilog.lines()
+                        val header = lines.first() + ":"
+                        val dependencies = lines.drop(1).map {
+                            it.split(" - ").also { check(it.size == 2) }
+                                .let { (name, desc) -> name to desc }
+                        }
+
+                        cell(Text(theme.warning(header)))
+                        for ((name, desc) in dependencies) cell(
+                            horizontalLayout {
+                                cell(Text(theme.info(name)).withPadding { left = 2; right = 1 })
+                                cell(desc)
+                            }
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun run() = Unit
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Diff.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Diff.kt
new file mode 100644
index 0000000..48ed373
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Diff.kt
@@ -0,0 +1,196 @@
+/*
+ * 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 bench.flame.diff.command
+
+import bench.flame.diff.config.Paths
+import bench.flame.diff.interop.execWithChecks
+import bench.flame.diff.interop.file
+import bench.flame.diff.interop.id
+import bench.flame.diff.interop.log
+import bench.flame.diff.interop.openFileInOs
+import bench.flame.diff.ui.promptProvideFile
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.groups.MutuallyExclusiveOptions
+import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions
+import com.github.ajalt.clikt.parameters.groups.single
+import com.github.ajalt.clikt.parameters.options.check
+import com.github.ajalt.clikt.parameters.options.convert
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.types.file
+import com.github.ajalt.clikt.parameters.types.int
+import java.io.File
+import kotlin.io.path.absolutePathString
+
+class Diff : CliktCommand(help = "Compare two saved trace files.") {
+    private val before by fileOption("before")
+    private val after by fileOption("after")
+
+    override fun run() {
+        Init.verifyDependencies()
+
+        val before: File = when {
+            before == null -> promptProvideFile("Provide the 'before' path",
+                defaultSrcDir = Paths.savedTracesDir.toFile())
+            else -> checkNotNull(before)
+        }
+        val after: File = when {
+            after == null -> promptProvideFile("Provide the 'after' path",
+                defaultSrcDir = Paths.savedTracesDir.toFile())
+            else -> checkNotNull(after)
+        }
+
+        // TODO: support custom labels
+        val diffDir: File = run {
+            fun createPath(number: Int): File {
+                val dstDirBaseName =
+                    "diff_${before.nameWithoutExtension}_${after.nameWithoutExtension}"
+                val suffix = if (number == 0) "" else "_${String.format("%03d", number)}"
+                return Paths.savedDiffsDir.resolve("$dstDirBaseName$suffix").toFile()
+            }
+
+            var i = 0
+            var result = createPath(i)
+            while (result.exists()) result = createPath(++i)
+            result.mkdirs()
+            result
+        }
+
+        val indexHtml = createDiffHtmlPage(before, after, diffDir)
+        openFileInOs(indexHtml)
+        log("Opened '${indexHtml.canonicalPath}' in the browser.", isStdErr = true)
+    }
+
+    private fun createDiffHtmlPage(beforeRaw: File, afterRaw: File, diffDir: File): File {
+        val beforeRawFolded = diffDir.resolve("before-raw.folded")
+        val afterRawFolded = diffDir.resolve("after-raw.folded")
+        val beforeRawSvg = diffDir.resolve("before-raw.svg")
+        val afterRawSvg = diffDir.resolve("after-raw.svg")
+
+        val afterDiffFolded = diffDir.resolve("after-diff.folded")
+        val beforeDiffFolded = diffDir.resolve("before-diff.folded")
+        val afterDiffSvg = diffDir.resolve("after-diff.svg")
+        val beforeDiffSvg = diffDir.resolve("before-diff.svg")
+
+        val indexHtml = diffDir.resolve("index.html")
+
+        collapseStacks(beforeRaw, beforeRawFolded)
+        collapseStacks(afterRaw, afterRawFolded)
+        createDiff(beforeRawFolded, afterRawFolded, afterDiffFolded)
+        createDiff(afterRawFolded, beforeRawFolded, beforeDiffFolded)
+
+        createFlameGraph(beforeRawFolded, beforeRawSvg)
+        createFlameGraph(afterRawFolded, afterRawSvg)
+        createFlameGraph(afterDiffFolded, afterDiffSvg)
+        createFlameGraph(beforeDiffFolded, beforeDiffSvg, negate = true)
+
+        createIndexHtml(
+            TraceDiffResult(beforeRawSvg, beforeDiffSvg, afterRawSvg, afterDiffSvg), indexHtml
+        )
+
+        for (tmpFile in listOf(beforeRawFolded, afterRawFolded, beforeDiffFolded, afterDiffFolded))
+            tmpFile.delete()
+        return indexHtml
+    }
+
+    private data class TraceDiffResult(
+        val beforeRawGraph: File,
+        val beforeDiffGraph: File,
+        val afterRawGraph: File,
+        val afterDiffGraph: File
+    )
+
+    private fun fileOption(role: String): MutuallyExclusiveOptions<File, File?> {
+        return mutuallyExclusiveOptions(
+            option(
+                "--$role-file",
+                help = "Path to the '$role' file."
+            ).file(mustExist = true, canBeDir = false),
+            option(
+                "--$role-id",
+                help = "Id of the '$role' file as per the **${List().commandName}** command"
+            ).int()
+                .convert { id: Int ->
+                    checkNotNull(List.savedTraces(Paths.savedTracesDir.toFile())
+                        .singleOrNull { it.id == id }) { "no saved trace with id '$id'" }
+                        .file
+                }
+                .check { f: File -> f.exists() && f.isFile },
+            name = "${role.take(1).uppercase()}${role.drop(1)} file",
+        ).single()
+    }
+
+    private fun collapseStacks(srcFile: File, dstFile: File) =
+        execWithChecks(
+            Paths.stackcollapsePy.absolutePathString(),
+            "--trace-offcpu=mixed-on-off-cpu",
+            "--event-filter", "cpu-clock", // this only allows for on-cpu; pending (b/325484390)
+            "-i",
+            srcFile.absolutePath,
+            cwd = Paths.stackcollapsePy.parent
+        ) {
+            dstFile.bufferedWriter().use { writer -> writer.write(it.stdOut) }
+        }
+
+    private fun createDiff(folded1: File, folded2: File, dstFile: File) =
+        execWithChecks(
+            Paths.difffoldedPl.absolutePathString(), "-n", folded1.absolutePath,
+            folded2.absolutePath
+        ) {
+            dstFile.bufferedWriter().use { writer -> writer.write(it.stdOut) }
+        }
+
+    private fun createFlameGraph(srcFile: File, dstFile: File, negate: Boolean = false) =
+        execWithChecks(
+            Paths.flamegraphPl.absolutePathString(),
+            "--title=${dstFile.nameWithoutExtension}"
+                .replace("before-raw", "base") // TODO(327208814)
+                .replace("after-raw", "current")
+                .replace("before-diff", "base vs current")
+                .replace("after-diff", "current vs base"),
+            "--fonttype=Roboto, sans-serif",
+            "--fontsize=13",
+            "--bgcolors=#f5f5f5",
+            "--minwidth=2.0",
+            "--width=1800", // TODO: autosize
+            "--inverted",
+            "--hash",
+            "--totaldiff",
+            "--mindeltapc=0.1",
+            "--fillopacity=0.65",
+            if (negate) "--negate" else "",
+            "--colors=grey",
+            "--countname=ns",
+            srcFile.absolutePath,
+        ) {
+            dstFile.bufferedWriter().use { writer -> writer.write(it.stdOut) }
+        }
+
+    private fun createIndexHtml(src: TraceDiffResult, dstFile: File) {
+        val rawContent = checkNotNull(
+            this::class.java.classLoader.getResourceAsStream("templates/index.html")
+        ).bufferedReader().use { it.readText() }
+        val content = rawContent
+            .replace("%before_raw_file%", src.beforeRawGraph.name)
+            .replace("%before_diff_file%", src.beforeDiffGraph.name)
+            .replace("%after_raw_file%", src.afterRawGraph.name)
+            .replace("%after_diff_file%", src.afterDiffGraph.name)
+            .replace("%before_raw_name%", "show base")
+            .replace("%before_diff_name%", "diff base vs curr")
+            .replace("%after_raw_name%", "show curr")
+            .replace("%after_diff_name%", "diff curr vs base")
+        dstFile.bufferedWriter().use { writer -> writer.write(content) }
+    }
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Init.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Init.kt
new file mode 100644
index 0000000..de5ba08
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Init.kt
@@ -0,0 +1,117 @@
+/*
+ * 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 bench.flame.diff.command
+
+import bench.flame.diff.config.Paths
+import bench.flame.diff.config.Uris
+import bench.flame.diff.interop.Os
+import bench.flame.diff.interop.execWithChecks
+import bench.flame.diff.interop.exitProcessWithError
+import bench.flame.diff.interop.output
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.mordant.markdown.Markdown
+import java.nio.file.Files
+import kotlin.io.path.absolutePathString
+import kotlin.io.path.exists
+import kotlin.io.path.name
+
+private const val verifyOnlyFlagName = "--verify-only"
+
+class Init : CliktCommand(help = "Initialize the tool's dependencies.") {
+    private val verifyOnly by option(
+        names = arrayOf(verifyOnlyFlagName),
+        help = "Verify the dependencies without initialising any."
+    ).flag(default = false)
+
+    override fun run() {
+        // Ensure we are on a Mac or Linux
+        if (!Os.isMac && !Os.isLinux) {
+            exitProcessWithError(
+                "unsupported operating system '${Os.rawName}', only Mac and Linux are supported."
+            )
+        }
+
+        /** Check out [Paths.flamegraphPl] from Git if not checked out */
+        if (!Paths.flamegraphPl.exists()) {
+            if (verifyOnly) exitProcessWithError(
+                Markdown(
+                    "dependency '${Paths.flamegraphDir.name}'" +
+                            " not satisfied. Run the **${this.commandName}** command to fix."
+                )
+            )
+            execWithChecks("mkdir", "-p", Paths.flamegraphDir.absolutePathString())
+            execWithChecks(
+                "git",
+                "clone",
+                "-n", // no checkout of HEAD is performed, ensuring we only use known revisions
+                Uris.flamegraphGitHub,
+                Paths.flamegraphDir.absolutePathString()
+            )
+            execWithChecks(
+                "git", "checkout",
+                "252e09ac0a56469c1f4a7d3440e5a7d352e1761e", // known good revision
+                cwd = Paths.flamegraphDir
+            )
+        }
+
+        /** Check that we can execute [Paths.flamegraphPl] */
+        execWithChecks(
+            Paths.flamegraphPl.absolutePathString(),
+            "--help",
+            checkIsSuccess = {
+                it.output.contains("USAGE") && it.output.contains(Paths.flamegraphPl.name)
+            }
+        )
+
+        /** Check that we can execute Simpleperf's [Paths.stackcollapsePy] */
+        if (!Paths.stackcollapsePy.exists()) {
+            if (verifyOnly) exitProcessWithError(
+                Markdown(
+                    "dependency '${Paths.simpleperfDir.name}'" +
+                            " not satisfied. Run the **${this.commandName}** command to fix."
+                )
+            )
+            val simplePerfDirPath = Paths.simpleperfDir.absolutePathString()
+            execWithChecks("mkdir", "-p", simplePerfDirPath)
+            val tmpSourceZip =
+                Files.createTempFile(Paths.simpleperfDir, "simpleperf-scripts-snapshot", ".tar.gz")
+            val tmpSourceZipPath = tmpSourceZip.absolutePathString()
+            execWithChecks("curl", Uris.simpleperfGoogleSource, "-o", tmpSourceZipPath)
+            execWithChecks(
+                "bash", "-c", "tar -xzf '$tmpSourceZipPath' -O | shasum -a256 | awk '{print $1}'"
+            ) {
+                val expected = "b9b5b41b270a7cb77be56b6a8c55930e2d565cf3a6e07617e198a2c336e19f91"
+                val actual = it.stdOut.trim()
+                check(actual == expected) {
+                    Files.delete(tmpSourceZip)
+                    "Simpleperf checksum mismatch. Expected: $expected. Actual: $actual"
+                }
+            }
+            execWithChecks("tar", "-xzf", tmpSourceZipPath, "-C", simplePerfDirPath)
+        }
+        execWithChecks(
+            Paths.stackcollapsePy.absolutePathString(),
+            "--help",
+            cwd = Paths.stackcollapsePy.parent
+        )
+    }
+
+    internal companion object {
+        fun verifyDependencies() = Init().main(arrayOf(verifyOnlyFlagName))
+    }
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/List.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/List.kt
new file mode 100644
index 0000000..ccda04a
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/List.kt
@@ -0,0 +1,56 @@
+/*
+ * 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 bench.flame.diff.command
+
+import bench.flame.diff.config.Paths
+import bench.flame.diff.interop.FileWithId
+import bench.flame.diff.interop.withId
+import bench.flame.diff.ui.printFileTable
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.mordant.markdown.Markdown
+import java.io.File
+import kotlin.collections.List
+
+class List : CliktCommand(help = "List all saved trace files.") {
+    override fun run() {
+        val savedTracesDir = Paths.savedTracesDir.toFile()
+        val traces = savedTraces(savedTracesDir)
+
+        if (traces.isEmpty()) {
+            echo(
+                Markdown(
+                    "No trace files saved. Run the **${Save().commandName}** command" +
+                            " to save traces for future comparison."
+                )
+            )
+            return
+        }
+
+        printFileTable(traces, savedTracesDir)
+    }
+
+    companion object {
+        /** Returns a list of saved traces sorted by 'most recently modified first' */
+        internal fun savedTraces(savedTracesDir: File): List<FileWithId> {
+            val files: Array<File> = when {
+                !savedTracesDir.exists() -> emptyArray<File>()
+                else -> savedTracesDir.listFiles() ?: emptyArray<File>()
+            }
+            files.sortWith(compareBy { f: File -> -f.lastModified() })
+            return files.asSequence().withId().toList()
+        }
+    }
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Open.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Open.kt
new file mode 100644
index 0000000..f210c8d
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Open.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 bench.flame.diff.command
+
+import bench.flame.diff.config.Paths
+import bench.flame.diff.interop.log
+import bench.flame.diff.interop.openFileInOs
+import bench.flame.diff.interop.withId
+import bench.flame.diff.ui.promptPickFile
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.mordant.markdown.Markdown
+
+class Open : CliktCommand(help = "Open a saved diff.") {
+    override fun run() {
+        val diffsDir = Paths.savedDiffsDir.toFile()
+        val diffFiles =
+            diffsDir.walkTopDown().filter { it.name == "index.html" }
+                .sortedBy { -it.lastModified() }.withId().toList()
+
+        if (diffFiles.isEmpty()) {
+            echo(
+                Markdown(
+                    "No diffs saved. Run the **${Diff().commandName}** command to compare traces."
+                )
+            )
+            return
+        }
+
+        val pickedDiff = promptPickFile(diffFiles, diffsDir)
+        openFileInOs(pickedDiff)
+        log("Opened '${pickedDiff.canonicalPath}' in the browser.", isStdErr = true)
+    }
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Save.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Save.kt
new file mode 100644
index 0000000..70fec93
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/command/Save.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 bench.flame.diff.command
+
+import bench.flame.diff.config.Paths
+import bench.flame.diff.interop.isValidFileName
+import bench.flame.diff.ui.promptOverwriteFile
+import bench.flame.diff.ui.promptProvideFile
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.terminal
+import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions
+import com.github.ajalt.clikt.parameters.groups.single
+import com.github.ajalt.clikt.parameters.options.check
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.types.file
+import com.github.ajalt.mordant.terminal.ConversionResult
+import java.io.File
+
+class Save : CliktCommand(help = "Save a trace file for future comparison.") {
+    private val src by mutuallyExclusiveOptions(
+        option("--src-file", help = "Path to a trace file.")
+            .file(mustExist = true, canBeDir = false),
+        option("--src-dir", help = "Path to a directory containing trace files.")
+            .file(mustExist = true, canBeFile = false),
+        name = "Trace source",
+    ).single()
+
+    private val dst by option(help = "Name for the saved trace file.")
+        .check { it.isValidFileName() }
+
+    private val pattern by option("--pattern", help = "Trace file name regex.")
+        .default(Paths.traceFileNamePattern)
+
+    override fun run() {
+        val src = src // allows for smart casts
+        val srcFile: File = when {
+            src != null && src.isFile -> src
+            else -> promptProvideFile("Provide trace source", pattern, src, Paths.outDir.toFile())
+        }
+        check(srcFile.exists() && srcFile.isFile)
+
+        val dstFile: File =
+            Paths.savedTracesDir.resolve(dst ?: promptDestinationName(srcFile.name)).toFile()
+        if (dstFile.exists()) promptOverwriteFile(dstFile).let { overwrite ->
+            if (!overwrite) return
+        }
+
+        dstFile.parentFile.mkdirs() // ensure destination dir is present
+        srcFile.copyTo(dstFile, overwrite = true)
+    }
+
+    private fun promptDestinationName(default: String? = null): String =
+        terminal.prompt("Provide destination file name", default) {
+            when {
+                it.isValidFileName() -> ConversionResult.Valid(it)
+                else -> ConversionResult.Invalid("Invalid value")
+            }
+        }!!
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/config/Paths.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/config/Paths.kt
new file mode 100644
index 0000000..94ad14f
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/config/Paths.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 bench.flame.diff.config
+
+import java.nio.file.Path
+import kotlin.io.path.name
+
+internal object Paths {
+    val currentDir get() = Path.of("").toAbsolutePath()
+    private val dependenciesDir get() = currentDir.resolve(".deps")
+    val savedTracesDir get() = currentDir.resolve("saved-traces")
+    val savedDiffsDir get() = currentDir.resolve("saved-diffs")
+    val outDir get() = frameworksSupportDir.parent.parent.resolve("out")
+    private val frameworksSupportDir get() = currentDir.parent.parent.also {
+        check(it.parent.name == "frameworks" && it.name == "support")
+    }
+    val simpleperfDir get() = dependenciesDir.resolve("simpleperf")
+    val stackcollapsePy get() = simpleperfDir.resolve("stackcollapse.py")
+    val flamegraphDir get() = dependenciesDir.resolve("Flamegraph")
+    val flamegraphPl get() = flamegraphDir.resolve("flamegraph.pl")
+    val difffoldedPl get() = flamegraphDir.resolve("difffolded.pl")
+
+    val traceFileNamePattern = ".*stackSampling.*\\.trace"
+}
+
+internal object Uris {
+    // Using jgielzak@ fork of https://github.com/brendangregg/FlameGraph until
+    // https://github.com/brendangregg/FlameGraph/pull/329 is merged.
+    val flamegraphGitHub = "https://github.com/gielzak/FlameGraph"
+
+    // Using a snapshot of Simpleperf until https://r.android.com/2980531 makes it into the NDK.
+    // Next check: 2024-Q4.
+    val simpleperfGoogleSource =
+        "https://android.googlesource.com/platform/system/extras/+archive/" +
+                "436786af3a357db5fd72cdac97903d6d587944a1/simpleperf/scripts.tar.gz"
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/File.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/File.kt
new file mode 100644
index 0000000..517b7ae
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/File.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 bench.flame.diff.interop
+
+import com.github.ajalt.clikt.core.CliktCommand
+import java.io.File
+import java.nio.file.InvalidPathException
+import java.nio.file.Path
+
+internal fun String.isValidFileName() = isNotBlank() && isValidPath() && isFileNameOnly()
+
+private fun String.isValidPath(): Boolean = try {
+    Path.of(this)
+    true
+} catch (_: InvalidPathException) {
+    false
+}
+
+private fun String.isFileNameOnly(): Boolean = isNotBlank() && isValidPath() &&
+        Path.of(this).parent == null
+
+internal fun CliktCommand.openFileInOs(target: File) = execWithChecks(
+    if (Os.isMac) "open" else "xdg-open", target.absolutePath
+)
+
+internal typealias FileWithId = IndexedValue<File>
+internal val FileWithId.id get() = index + 1
+internal val FileWithId.file get() = value
+internal fun Sequence<File>.withId(): Sequence<FileWithId> = withIndex()
diff --git a/appcompat/appcompat/src/main/java/DeleteMe.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Os.kt
similarity index 64%
copy from appcompat/appcompat/src/main/java/DeleteMe.kt
copy to development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Os.kt
index 38f8b7a..18b914d 100644
--- a/appcompat/appcompat/src/main/java/DeleteMe.kt
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Os.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -13,5 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package bench.flame.diff.interop
 
-// This file exists to trick AGP/lint to work around b/234865137
+internal object Os {
+    val isMac: Boolean get() = rawName.lowercase().contains("mac os")
+    val isLinux: Boolean get() = rawName.lowercase().contains("linux")
+    val rawName: String get() = System.getProperty("os.name")
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Process.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Process.kt
new file mode 100644
index 0000000..cf61871
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Process.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 bench.flame.diff.interop
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.mordant.rendering.Widget
+import kotlin.system.exitProcess
+
+internal fun CliktCommand.exitProcessWithError(error: String): Nothing =
+    exitProcessWithErrorImpl(error)
+
+internal fun CliktCommand.exitProcessWithError(error: Widget): Nothing =
+    exitProcessWithErrorImpl(error)
+
+internal fun CliktCommand.log(message: String, isStdErr: Boolean = false): Unit =
+    logImpl(message, isStdErr)
+
+private fun CliktCommand.exitProcessWithErrorImpl(error: Any): Nothing {
+    logImpl(error, true)
+    exitProcess(1)
+}
+
+private fun CliktCommand.logImpl(error: Any, isStdErr: Boolean) {
+    echo("bench-flame-diff: ", err = true, trailingNewline = false)
+    echo(error, err = isStdErr)
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Shell.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Shell.kt
new file mode 100644
index 0000000..a039c90
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/interop/Shell.kt
@@ -0,0 +1,77 @@
+/*
+ * 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 bench.flame.diff.interop
+
+import bench.flame.diff.config.Paths
+import com.github.ajalt.clikt.core.CliktCommand
+import com.zaxxer.nuprocess.NuAbstractProcessHandler
+import com.zaxxer.nuprocess.NuProcessBuilder
+import java.nio.ByteBuffer
+import java.nio.file.Path
+import java.util.concurrent.TimeUnit
+
+internal class Shell(private val cwd: Path) {
+    fun exec(vararg command: String): ExecutionResult {
+        val handler = object : NuAbstractProcessHandler() {
+            var outs = StringBuilder()
+            var errs = StringBuilder()
+            var exitCode: Int = 0
+
+            override fun onExit(exitCode: Int) {
+                this.exitCode = exitCode
+            }
+
+            override fun onStdout(buffer: ByteBuffer, closed: Boolean) = consumeLines(buffer, outs)
+
+            override fun onStderr(buffer: ByteBuffer, closed: Boolean) = consumeLines(buffer, errs)
+
+            private fun consumeLines(srcBuffer: ByteBuffer, destination: StringBuilder) {
+                val dstBuffer = ByteArray(srcBuffer.remaining())
+                srcBuffer.get(dstBuffer)
+                destination.append(String(dstBuffer))
+            }
+        }
+
+        NuProcessBuilder(command.asList()).also {
+            it.setCwd(cwd)
+            it.setProcessListener(handler)
+        }.start().waitFor(0, TimeUnit.SECONDS)
+
+        return ExecutionResult(handler.exitCode, handler.outs.toString(), handler.errs.toString())
+    }
+
+    data class ExecutionResult(val exitCode: Int, val stdOut: String, val stdErr: String)
+}
+
+internal val Shell.ExecutionResult.output get() = stdOut + stdErr
+
+internal fun CliktCommand.execWithChecks(
+    vararg command: String,
+    cwd: Path = Paths.currentDir,
+    checkIsSuccess: (Shell.ExecutionResult) -> Boolean = { it.exitCode == 0 },
+    onError: (Shell.ExecutionResult) -> Unit = {
+        exitProcessWithError(
+            "error occurred while executing command '${command.asList()}'." +
+                    "  \nexit code: ${it.exitCode}" +
+                    "  \nstdout: ${it.stdOut}" +
+                    "  \nstderr: ${it.stdErr}"
+        )
+    },
+    onSuccess: (Shell.ExecutionResult) -> Unit = { },
+) {
+    val result = Shell(cwd).exec(*command)
+    if (!checkIsSuccess(result)) onError(result) else onSuccess(result)
+}
diff --git a/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/ui/Terminal.kt b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/ui/Terminal.kt
new file mode 100644
index 0000000..b78f809
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/kotlin/bench/flame/diff/ui/Terminal.kt
@@ -0,0 +1,94 @@
+/*
+ * 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 bench.flame.diff.ui
+
+import bench.flame.diff.interop.FileWithId
+import bench.flame.diff.interop.exitProcessWithError
+import bench.flame.diff.interop.file
+import bench.flame.diff.interop.id
+import bench.flame.diff.interop.withId
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.terminal
+import com.github.ajalt.mordant.rendering.Whitespace
+import com.github.ajalt.mordant.table.table
+import com.github.ajalt.mordant.terminal.ConversionResult
+import com.github.ajalt.mordant.terminal.YesNoPrompt
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+
+internal fun CliktCommand.printFileTable(files: List<FileWithId>, trimBaseDir: File?) {
+    val trimPrefix: String = if (trimBaseDir == null) "" else "${trimBaseDir.canonicalPath}/"
+    terminal.println(table {
+        whitespace = Whitespace.PRE_WRAP
+        header { row("Id", "Name", "Last Modified") }
+        body {
+            files.map {
+                val lastModified = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+                    .format(Date(it.file.lastModified()))
+                row(it.id, it.file.canonicalPath.removePrefix(trimPrefix), lastModified)
+            }
+        }
+    })
+}
+
+internal fun CliktCommand.promptPickFile(candidates: List<FileWithId>, trimBaseDir: File?): File {
+    printFileTable(candidates, trimBaseDir)
+    return terminal.prompt("Choose a file by id") {
+        val number = it.toIntOrNull()
+        val min = candidates.minOf { f -> f.id }
+        val max = candidates.maxOf { f -> f.id }
+        when {
+            number == null || candidates.none { f -> f.id == number } ->
+                ConversionResult.Invalid("Choose a number between $min and $max")
+            else ->
+                ConversionResult.Valid(candidates.single { f -> f.id == number }.file)
+        }
+    }!!
+}
+
+internal fun CliktCommand.promptProvideFile(
+    prompt: String,
+    pattern: String = ".*",
+    srcDir: File? = null,
+    defaultSrcDir: File? = null
+): File {
+    check(srcDir == null || srcDir.isDirectory)
+    check(defaultSrcDir == null || defaultSrcDir.isDirectory)
+    val baseDir = run {
+        val src = srcDir ?: terminal.prompt(prompt, defaultSrcDir) {
+            when {
+                it.isBlank() || !File(it).exists() -> ConversionResult.Invalid("Invalid value")
+                else -> ConversionResult.Valid(File(it))
+            }
+        }!!
+        if (src.isFile) return src
+        src
+    }
+
+    check(baseDir.isDirectory)
+    echo("Looking for files in '${baseDir.absolutePath}' matching '$pattern'...")
+    val candidates = baseDir.walkTopDown().filter { it.isFile && it.name.matches(Regex(pattern)) }
+        .sortedBy { -it.lastModified() }.withId().toList()
+
+    if (candidates.isEmpty()) exitProcessWithError("No files matching '$pattern' in '$baseDir'")
+    return promptPickFile(candidates, baseDir)
+}
+
+internal fun CliktCommand.promptOverwriteFile(file: File): Boolean = YesNoPrompt(
+    "Overwrite existing file '${file.absolutePath}'",
+    terminal
+).ask()!!
diff --git a/development/bench-flame-diff/app/src/main/resources/templates/index.html b/development/bench-flame-diff/app/src/main/resources/templates/index.html
new file mode 100644
index 0000000..90ee7c4
--- /dev/null
+++ b/development/bench-flame-diff/app/src/main/resources/templates/index.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Diff</title>
+
+    <link
+      href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css"
+      rel="stylesheet"
+    />
+    <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
+    <style>
+      .spacer {
+        margin: 10px;
+        display: inline;
+      }
+      .top-nav-title {
+        font-family: 'Courier New', monospace;
+        font-size: small;
+        margin-left: 2px;
+      }
+      .svg {
+        margin-top: 7px
+      }
+    </style>
+  </head>
+  <body>
+    <div>
+      <div>
+        <span class="top-nav-title">bench-flame-diff</span>
+        <div class="spacer"></div>
+        <button class="button" onclick="openTab('%before_raw_name%')">%before_raw_name%</button>
+        <button class="button" onclick="openTab('%before_diff_name%')">%before_diff_name%</button>
+        <div class="spacer">|</div>
+        <button class="button" onclick="openTab('%after_raw_name%')">%after_raw_name%</button>
+        <button class="button" onclick="openTab('%after_diff_name%')">%after_diff_name%</button>
+        <div class="spacer">|</div>
+        <button class="button" onclick="window.open('http://go/bench-flame-diff-readme')">help</button>
+      </div>
+      <div id="%before_raw_name%" class="svg">
+        <object type="image/svg+xml" data="%before_raw_file%">Failed to display the file: %before_raw_file%</object>
+      </div>
+      <div id="%before_diff_name%" class="svg">
+        <object type="image/svg+xml" data="%before_diff_file%">Failed to display the file: %before_diff_file%</object>
+      </div>
+      <div id="%after_raw_name%" class="svg">
+        <object type="image/svg+xml" data="%after_raw_file%">Failed to display the file: %after_raw_file%</object>
+      </div>
+      <div id="%after_diff_name%" class="svg">
+        <object type="image/svg+xml" data="%after_diff_file%">Failed to display the file: %after_diff_file%</object>
+      </div>
+    </div>
+
+    <script>
+      mdc.ripple.MDCRipple.attachTo(document.querySelector(".button"));
+
+      function openTab(tabName) {
+        let tab = document.getElementsByClassName("svg");
+        for (let i = 0; i < tab.length; i++) tab[i].style.display = "none";
+        document.getElementById(tabName).style.display = "block";
+      }
+
+      openTab("%after_diff_name%");
+    </script>
+  </body>
+</html>
diff --git a/development/bench-flame-diff/assets/illustration-diff.webp b/development/bench-flame-diff/assets/illustration-diff.webp
new file mode 100644
index 0000000..f7479db
--- /dev/null
+++ b/development/bench-flame-diff/assets/illustration-diff.webp
Binary files differ
diff --git a/development/bench-flame-diff/bench-flame-diff.sh b/development/bench-flame-diff/bench-flame-diff.sh
new file mode 100755
index 0000000..66a6e40
--- /dev/null
+++ b/development/bench-flame-diff/bench-flame-diff.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+./gradlew --quiet installDist && ./app/build/install/bench-flame-diff/bin/bench-flame-diff "$@"
diff --git a/development/bench-flame-diff/generate-completion.sh b/development/bench-flame-diff/generate-completion.sh
new file mode 100755
index 0000000..99d29ec
--- /dev/null
+++ b/development/bench-flame-diff/generate-completion.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+function generate_completion_files() {
+    for shell in bash fish zsh; do
+        dst=completion_$shell.sh
+        (export _BENCH_FLAME_DIFF_COMPLETE=$shell; ./bench-flame-diff.sh | sed -E "s_bench-flame-diff( |$)_bench-flame-diff.sh\1_" >| $dst)
+        echo "Generated $dst"
+    done
+}
+
+generate_completion_files
diff --git a/development/bench-flame-diff/gradle/libs.versions.toml b/development/bench-flame-diff/gradle/libs.versions.toml
new file mode 100644
index 0000000..a157c04
--- /dev/null
+++ b/development/bench-flame-diff/gradle/libs.versions.toml
@@ -0,0 +1,2 @@
+[plugins]
+jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.20" }
diff --git a/development/bench-flame-diff/gradle/verification-metadata.xml b/development/bench-flame-diff/gradle/verification-metadata.xml
new file mode 100644
index 0000000..35f3a46
--- /dev/null
+++ b/development/bench-flame-diff/gradle/verification-metadata.xml
@@ -0,0 +1,423 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
+   <configuration>
+      <verify-metadata>true</verify-metadata>
+      <verify-signatures>false</verify-signatures>
+   </configuration>
+   <components>
+      <component group="com.github.ajalt.clikt" name="clikt" version="4.2.1">
+         <artifact name="clikt-4.2.1.module">
+            <sha256 value="0f134f26129ed70b6694661176a54e702ffe325b737130fcc8a5f0ea20cf24b9" origin="Generated by Gradle">
+               <also-trust value="1f134f26129ed70b6694661176a54e702ffe325b737130fcc8a5f0ea20cf24b9"/>
+            </sha256>
+         </artifact>
+         <artifact name="clikt-metadata.jar">
+            <sha256 value="934ed6297dfa5945ddb07622182b3737a6d3b95c26b44efc41a0a86db614d25f" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.github.ajalt.clikt" name="clikt-jvm" version="4.2.1">
+         <artifact name="clikt-jvm-4.2.1.module">
+            <sha256 value="4e680f0706d80a50e9a7f17580a8cbaa733cb35c5fb6651adbef8c653a841d09" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="clikt-jvm.jar">
+            <sha256 value="efdf5fc38dfa2953a2e1995cecba0dc281aab35385d595fe042155158c61647f" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.github.ajalt.colormath" name="colormath" version="3.3.1">
+         <artifact name="colormath-3.3.1.module">
+            <sha256 value="01d4d68d02f2a32d3032472f88b1d2c1e0c8bfab3bdd34e003db2c0319ead0ba" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="colormath-metadata.jar">
+            <sha256 value="b80bf47aa347db77b7e0fb4518af7e84252027c47e2c443c38b04fa979d8b97d" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.github.ajalt.colormath" name="colormath-jvm" version="3.3.1">
+         <artifact name="colormath-jvm-3.3.1.module">
+            <sha256 value="2bf76054dcb23a76e52651906bc6a25055ba6ed05afe36b79208309f2be8924f" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="colormath-jvm.jar">
+            <sha256 value="2613283415e2e12661697dc7295adada0f60ec17ecfcaf3ef1c4ee0fdb788913" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.github.ajalt.mordant" name="mordant" version="2.2.0">
+         <artifact name="mordant-2.2.0.module">
+            <sha256 value="3d9b2c0e1ea43c63cdc99bbea9aa38b543a6c8c889528ebd2a5e4e53e2341e27" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="mordant-metadata.jar">
+            <sha256 value="6857ff8610461a0c0df17318c5898b86272173cacec76a2309c1c2a58305385c" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.github.ajalt.mordant" name="mordant-jvm" version="2.2.0">
+         <artifact name="mordant-jvm-2.2.0.module">
+            <sha256 value="a4112264923207ae621642498b572fa688a63879a17a87c2dfb760512ef539f2" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="mordant-jvm.jar">
+            <sha256 value="2fa59e5b81afcc71b6ab6128b27676ffc611d0a3c182ebc0e437b3666ab56201" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.google.code.gson" name="gson" version="2.9.1">
+         <artifact name="gson-2.9.1.jar">
+            <sha256 value="378534e339e6e6d50b1736fb3abb76f1c15d1be3f4c13cec6d536412e23da603" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="gson-2.9.1.pom">
+            <sha256 value="e5966323d7142570b37a4be979e21bc2dae848107e4dc416d8f44d9aa3f02903" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.google.code.gson" name="gson-parent" version="2.9.1">
+         <artifact name="gson-parent-2.9.1.pom">
+            <sha256 value="7ca0845e73685618de3e46bd3434d03a4a373d520fab93a680318ad6c8cb2a79" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="com.zaxxer" name="nuprocess" version="2.0.6">
+         <artifact name="nuprocess-2.0.6.jar">
+            <sha256 value="978c9224c38cddece6b572d2743770208e6c29b4c7710db92089247ce2125aef" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="nuprocess-2.0.6.pom">
+            <sha256 value="6d4c0ea8e76c3c372dfac7a206654ffcd4e37eea43d647fbd264a49bbcd2e352" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="it.unimi.dsi" name="fastutil-core" version="8.5.12">
+         <artifact name="fastutil-core-8.5.12.jar">
+            <sha256 value="f31c20f5b06312f3d5e06e6160a32e274d819aa6cebf27528b26b6b5c0c1df19" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="fastutil-core-8.5.12.pom">
+            <sha256 value="839243bbe61611f937bb0b5d9b31d0c8e096c7d0d679922cf6ed39f82c6ee0d2" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="net.java.dev.jna" name="jna" version="5.11.0">
+         <artifact name="jna-5.11.0.jar">
+            <sha256 value="e2bce99e4aefd4dab097019a799d317cb3b5d56c3ddd7984c69a772dceed0dd3" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="jna-5.11.0.pom">
+            <sha256 value="5fde1ae98a30bceb6516dc8f7e19a3ee504143ceeb9b2ba4ae107105dfe3c326" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="net.java.dev.jna" name="jna" version="5.13.0">
+         <artifact name="jna-5.13.0.jar">
+            <sha256 value="66d4f819a062a51a1d5627bffc23fac55d1677f0e0a1feba144aabdd670a64bb" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="jna-5.13.0.pom">
+            <sha256 value="f515c2578178f45247ecca7a9e1db109531b1c42f2424e253ceeb0f6b8d42374" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.gradle.toolchains" name="foojay-resolver" version="0.7.0">
+         <artifact name="foojay-resolver-0.7.0.jar">
+            <sha256 value="93672b4740a0fdbfbb5baf08353db8ae9a9bb25f6b10a93c078126c717db3ac0" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="foojay-resolver-0.7.0.module">
+            <sha256 value="ed6746a09f32bfaddb90b029102ae62326e248129a68b74662a4a433d55314b8" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.gradle.toolchains.foojay-resolver-convention" name="org.gradle.toolchains.foojay-resolver-convention.gradle.plugin" version="0.7.0">
+         <artifact name="org.gradle.toolchains.foojay-resolver-convention.gradle.plugin-0.7.0.pom">
+            <sha256 value="c8a443e2faef876f338a391233c20a91ad5b83f69c1c6ba20d39a7b39106240a" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains" name="annotations" version="13.0">
+         <artifact name="annotations-13.0.jar">
+            <sha256 value="ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="annotations-13.0.pom">
+            <sha256 value="965aeb2bedff369819bdde1bf7a0b3b89b8247dd69c88b86375d76163bb8c397" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains" name="markdown" version="0.5.2">
+         <artifact name="markdown-0.5.2.module">
+            <sha256 value="6e91401a6a4b6e77356ae2337630e8a2f2dc41ac261b705b3a966ffc623f23e4" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="markdown-metadata-0.5.2.jar">
+            <sha256 value="1c6b1b6bb9ce83048b8648990ac1d3427bfacfebc8e58e32ee9043ddd0dc3a2a" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains" name="markdown-jvm" version="0.5.2">
+         <artifact name="markdown-jvm-0.5.2.jar">
+            <sha256 value="726484477260a552dc7c19b09d4656c48ae3b10db2725000e182d6159529b46e" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="markdown-jvm-0.5.2.module">
+            <sha256 value="7f303666042d78d24c555120c882478c309a228ee92b51bd71adbffa49c33b50" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.intellij.deps" name="trove4j" version="1.0.20200330">
+         <artifact name="trove4j-1.0.20200330.jar">
+            <sha256 value="c5fd725bffab51846bf3c77db1383c60aaaebfe1b7fe2f00d23fe1b7df0a439d" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="trove4j-1.0.20200330.pom">
+            <sha256 value="87721cbaa65a3c97d8b1ba9d207840f164c9fe38759fc9ea10ffe26565f8d3e9" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-android-extensions" version="1.9.20">
+         <artifact name="kotlin-android-extensions-1.9.20.jar">
+            <sha256 value="b771239469f0af07e180f746cfde6a7956c2b6261e1ae20e5b1d620a0dd29bff" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-android-extensions-1.9.20.pom">
+            <sha256 value="0d7adb86de59d5de3807eded1b095bc9e14b00adf1dc250a5f62c9c3f54183e6" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-build-common" version="1.9.20">
+         <artifact name="kotlin-build-common-1.9.20.jar">
+            <sha256 value="17319416d0fa12cd77a9f365f8b8cb9c616953883368a5c7f529cf082da9e98d" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-build-common-1.9.20.pom">
+            <sha256 value="8aadfa0eefe9ed27aa6ee17e748f4d0ec4afa6cdfd81fa4939077ac0631d5046" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-build-tools-api" version="1.9.20">
+         <artifact name="kotlin-build-tools-api-1.9.20.jar">
+            <sha256 value="c722948c568352cdc19dc8a8b245d14aae507d4dcffde6a7b26c535c472c1b17" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-build-tools-api-1.9.20.pom">
+            <sha256 value="3fbc370f8587a3d8e80c69f2dccd65c55173bb44b9d3942e15536bce3ccdc1a6" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-build-tools-impl" version="1.9.20">
+         <artifact name="kotlin-build-tools-impl-1.9.20.jar">
+            <sha256 value="b7377a08d67dcddcbe4f7930d8cb0f7d0055789fbb30efdbe97008405d1f026d" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-build-tools-impl-1.9.20.pom">
+            <sha256 value="44f80a02ea520d24c8473ec27879e3b16b873381c057757e1b8f51bee07d5cb1" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-compiler-embeddable" version="1.9.20">
+         <artifact name="kotlin-compiler-embeddable-1.9.20.jar">
+            <sha256 value="a25024fe5da8440de01af045c4fcb954a22f078738ec02616085f0cfc57b2702" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-compiler-embeddable-1.9.20.pom">
+            <sha256 value="c7d8dfc89e621442504506b9492fb763117406120ce91072bc067ff96d264c23" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-compiler-runner" version="1.9.20">
+         <artifact name="kotlin-compiler-runner-1.9.20.jar">
+            <sha256 value="49769c046f8d392654a4ab52af795455bd41e88d8392aeab9028f0edd5e8d50b" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-compiler-runner-1.9.20.pom">
+            <sha256 value="5ca27d9b2104e3230e7f81abaa6eb611bf3986d74a00e565e25eeb18610d0d8a" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-daemon-client" version="1.9.20">
+         <artifact name="kotlin-daemon-client-1.9.20.jar">
+            <sha256 value="582230cbcfd65d36b94bc9d127f90024b8cf17dfa4a67ef6a929f14c6c27661c" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-daemon-client-1.9.20.pom">
+            <sha256 value="d2a418163a6948d72b04cbe35203a2e305e1c38d568090170ac872f881fa9381" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-daemon-embeddable" version="1.9.20">
+         <artifact name="kotlin-daemon-embeddable-1.9.20.jar">
+            <sha256 value="a939cb5d6ee2a758c9285bd9f3286824beabe12d9a4b5f49f784d0bca329dea5" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-daemon-embeddable-1.9.20.pom">
+            <sha256 value="cd892de769cc20b9798e22538d9963074b05f1c448f2e01ab77547eeb6597d41" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-1.9.20-gradle81.jar">
+            <sha256 value="04910fca652f8dbe804a49c8e72971bf641d03cd8b45a065ba4ce10c6584eaac" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-1.9.20.module">
+            <sha256 value="55525c3e03692529c8f196607789adc8eeb1a207ea131f8d9c1c32b497f7e636" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-annotations" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-annotations-1.9.20.jar">
+            <sha256 value="2a5c3622e2468584d1ab7dab3acd8ffb60403b637dd0603af675e26d3a054329" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-annotations-1.9.20.pom">
+            <sha256 value="f4bfa0aa2c2392e4f23e26b7f1a419865beb76a016d8180872bb086cc003594e" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-api" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-api-1.9.20.jar">
+            <sha256 value="287c26765f8692e5eb5505854126819cfbb0c7d5d49bbe5f45771427ea19913d" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-api-1.9.20.module">
+            <sha256 value="483e7577f4e9e2d3ca17f6aa64f4655d4603c2b1d0e6dfd5951552ef40d5d745" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-idea" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-idea-1.9.20.jar">
+            <sha256 value="8d1af87632d95148f122a9fa0ae2903c19ee6fab7d01e017f76e0d2c9a022c20" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-idea-1.9.20.module">
+            <sha256 value="1e6b39381f3a4f9b1497454e825f8942e0c9c6ec3dc1b8a2fd636e237195f8ab" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-idea-proto" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-idea-proto-1.9.20.jar">
+            <sha256 value="c67b0d8849febdd9a964eda0bd167c167c4d056ca8dd389241d92e1d763c9490" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-idea-proto-1.9.20.pom">
+            <sha256 value="d4010d4b3bb9e9240f3de0e78e8df5a581236f33d62e8070c8fdf25317703594" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-model" version="1.9.20">
+         <artifact name="kotlin-gradle-plugin-model-1.9.20.jar">
+            <sha256 value="7f930f0e454b75818f5f8976ba515f3aec887671a5fe85380ac97f05da9986a7" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugin-model-1.9.20.module">
+            <sha256 value="3996aaab9b546ccf455b1caf0c316ea32e9ce646feb54e973af4d69fb605f248" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugins-bom" version="1.9.20">
+         <artifact name="kotlin-gradle-plugins-bom-1.9.20.module">
+            <sha256 value="7720f845cfe319aa1a6e5b23387e6920a35e99ae4218ca0f6f6e07fd1713093c" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-gradle-plugins-bom-1.9.20.pom">
+            <sha256 value="6f3ba42a3e981700284c956146ef4d716b89adbe5d803ab92553dec216344330" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-klib-commonizer-api" version="1.9.20">
+         <artifact name="kotlin-klib-commonizer-api-1.9.20.jar">
+            <sha256 value="89b6260828953042e310a52592aa5b595f5f89b641cc6a3d3a8155ef92d88ffe" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-klib-commonizer-api-1.9.20.pom">
+            <sha256 value="9780fdc5679a4db1a170fa5210f561ceb2ad46b6f829c86ac31fc78cf9296c65" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-klib-commonizer-embeddable" version="1.9.20">
+         <artifact name="kotlin-klib-commonizer-embeddable-1.9.20.jar">
+            <sha256 value="8b36530fb4b68198c7733adbe3749d481af2bd9c0b03e89d88bfe93e12fda0f9" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-klib-commonizer-embeddable-1.9.20.pom">
+            <sha256 value="ef24d0adecfe9adcd33af7a8f3eeec022aebb478612f3602f47c6a33a5b4a24b" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-native-utils" version="1.9.20">
+         <artifact name="kotlin-native-utils-1.9.20.jar">
+            <sha256 value="b0f92bc9253a907f0ce285328643fe8a36c27ed494b5c5919ee09c2926d8e8d2" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-native-utils-1.9.20.pom">
+            <sha256 value="f3ef597806931b40ceed81d91b69e7cc7a7867ecced92db44bbfe6124776c5bc" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-project-model" version="1.9.20">
+         <artifact name="kotlin-project-model-1.9.20.jar">
+            <sha256 value="261a9b40e240e259ac359c16938002ecc6c08434a5c6e5e5bffee242a3c50218" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-project-model-1.9.20.pom">
+            <sha256 value="e0584405d5beef440a66b290d6e4c0dd43ea965dc35b770c1ea3264a87d2733c" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-reflect" version="1.6.10">
+         <artifact name="kotlin-reflect-1.6.10.jar">
+            <sha256 value="3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-reflect-1.6.10.pom">
+            <sha256 value="57905524274a00ae028aaccc27283f6bc5925a934a046c1cc5d06c8ee4d6d5a9" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-script-runtime" version="1.9.20">
+         <artifact name="kotlin-script-runtime-1.9.20.jar">
+            <sha256 value="a26a6256a76f766ab8bacdb409b3f8c940d999712a8e88864252b678d66bab9e" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-script-runtime-1.9.20.pom">
+            <sha256 value="bcd227d293742c511a24bb2c56822d95f9638af5135b973e7cda3e8a76686579" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-scripting-common" version="1.9.20">
+         <artifact name="kotlin-scripting-common-1.9.20.jar">
+            <sha256 value="5aa08477cb73f7927413aec683a4aa3b3f99e87be0630255ce697452a1a42d65" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-scripting-common-1.9.20.pom">
+            <sha256 value="187e9f3b74bd56a083649818c7b9a40f8bc23f62012b08a7bc99c3fa886bff03" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-scripting-compiler-embeddable" version="1.9.20">
+         <artifact name="kotlin-scripting-compiler-embeddable-1.9.20.jar">
+            <sha256 value="2181dd0c4d52c6f696ad9f17934233790f4d68234b1418d6376fda7e5c374c4e" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-scripting-compiler-embeddable-1.9.20.pom">
+            <sha256 value="266128dbf435e3aebfee65357dde102c9ed929dfeb0901e56be78ca148c573f7" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-scripting-compiler-impl-embeddable" version="1.9.20">
+         <artifact name="kotlin-scripting-compiler-impl-embeddable-1.9.20.jar">
+            <sha256 value="dc9ab6f69c592ad1f1d2e2b994b97509d0ee09480bea6bc771eeeef3071eb817" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-scripting-compiler-impl-embeddable-1.9.20.pom">
+            <sha256 value="6dba3003e8cadcb14992dff134e866cab5905eab80e27023de41c26385838198" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-scripting-jvm" version="1.9.20">
+         <artifact name="kotlin-scripting-jvm-1.9.20.jar">
+            <sha256 value="809f73bdd4dd7766ae1ef2ced968896ce9c03d6a5fe6de6f6799778851f75bd3" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-scripting-jvm-1.9.20.pom">
+            <sha256 value="bf881c6e2fc6a37ea9d09ffe98ca072dfcda8dc8288fd89b24cadc60b14db75e" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.9.20">
+         <artifact name="kotlin-stdlib-1.9.20-all.jar">
+            <sha256 value="cec38bc3302e72a8aaf9cde436b5a9071ee0331e2ad05e84d8bb897334d7e9d4" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-1.9.20.jar">
+            <sha256 value="28a35bcdff46d864f80f346a617e486284b208d17378c41900dfb1de95a90e6c" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-1.9.20.module">
+            <sha256 value="dccaa5d315470fab3920502886bbb85f2da6c86102c65d9c04410544eedb2019" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.9.20">
+         <artifact name="kotlin-stdlib-common-1.9.20.module">
+            <sha256 value="858828bc5191b9e602affa14e01d66489dafb08c4c18d2faee3cbed7ba7d9992" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk7" version="1.9.10">
+         <artifact name="kotlin-stdlib-jdk7-1.9.10.jar">
+            <sha256 value="ac6361bf9ad1ed382c2103d9712c47cdec166232b4903ed596e8876b0681c9b7" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-jdk7-1.9.10.pom">
+            <sha256 value="c7fa67c7961320b89d85a3ca59a2e18c2c65850845595dcae4b46af6945edcd5" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk8" version="1.9.10">
+         <artifact name="kotlin-stdlib-jdk8-1.9.10.jar">
+            <sha256 value="a4c74d94d64ce1abe53760fe0389dd941f6fc558d0dab35e47c085a11ec80f28" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-jdk8-1.9.10.pom">
+            <sha256 value="5f4b94dd3065a7764c37fa15de2ad6d81f40d59f8cb33f17d181c6384fb7a72e" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-tooling-core" version="1.9.20">
+         <artifact name="kotlin-tooling-core-1.9.20.jar">
+            <sha256 value="8938eb97e36320daa3e6fb2a60fd2a05b232ff4a557173c5019f045b8832d9f4" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-tooling-core-1.9.20.pom">
+            <sha256 value="73daf491403928b15c4930957ae001acf04f6b3a4bc28fe4aa5777ef09709ed6" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-util-io" version="1.9.20">
+         <artifact name="kotlin-util-io-1.9.20.jar">
+            <sha256 value="c74fdaaae9d79fdf03327ee8738251e024b24b24d8b5377a1a429ac3b7f72cca" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-util-io-1.9.20.pom">
+            <sha256 value="adb1f435b30b22563aa0ae57d3f6396245988487a79eafae38e6676fd8202a89" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-util-klib" version="1.9.20">
+         <artifact name="kotlin-util-klib-1.9.20.jar">
+            <sha256 value="c453efe27a0632d16151bfdf0084a12b8cc019fd2cb342e2b8892accce4e91b2" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-util-klib-1.9.20.pom">
+            <sha256 value="56ebfa3e92f00a25f3c0bfedc9a631095e2862c8ff9ba598f93e691a409eedcd" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin.jvm" name="org.jetbrains.kotlin.jvm.gradle.plugin" version="1.9.20">
+         <artifact name="org.jetbrains.kotlin.jvm.gradle.plugin-1.9.20.pom">
+            <sha256 value="8a66a9ecd1d2de82f70e280bbcf5eea0a54a7af63a36567ddea5ac1cf7f19b68" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.5.0">
+         <artifact name="kotlinx-coroutines-core-jvm-1.5.0.jar">
+            <sha256 value="78d6cc7135f84d692ff3752fcfd1fa1bbe0940d7df70652e4f1eaeec0c78afbb" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlinx-coroutines-core-jvm-1.5.0.module">
+            <sha256 value="c885dd0281076c5843826de317e3cbcdc3d8859dbeef53ae1cfacd1b9c60f96e" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.sonatype.oss" name="oss-parent" version="9">
+         <artifact name="oss-parent-9.pom">
+            <sha256 value="fb40265f982548212ff82e362e59732b2187ec6f0d80182885c14ef1f982827a" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+   </components>
+</verification-metadata>
diff --git a/development/bench-flame-diff/gradle/wrapper b/development/bench-flame-diff/gradle/wrapper
new file mode 120000
index 0000000..166ffc0
--- /dev/null
+++ b/development/bench-flame-diff/gradle/wrapper
@@ -0,0 +1 @@
+../../../gradle/wrapper
\ No newline at end of file
diff --git a/development/bench-flame-diff/gradlew b/development/bench-flame-diff/gradlew
new file mode 120000
index 0000000..d9f055c
--- /dev/null
+++ b/development/bench-flame-diff/gradlew
@@ -0,0 +1 @@
+../../playground-common/gradlew
\ No newline at end of file
diff --git a/development/bench-flame-diff/settings.gradle.kts b/development/bench-flame-diff/settings.gradle.kts
new file mode 100644
index 0000000..eee15d4
--- /dev/null
+++ b/development/bench-flame-diff/settings.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+    // Apply the foojay-resolver plugin to allow automatic download of JDKs
+    id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
+}
+
+rootProject.name = "bench-flame-diff"
+include("app")
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 6f6e4f7..083ad78 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -395,7 +395,7 @@
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/ComplicationSlot\.kt:[0-9]+ Unable to find reference @param supportedTypes in DClass Builder\. Are you trying to refer to something not visible to users\?
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/Renderer\.kt:[0-9]+ Link does not resolve for @throws Renderer\.GlesException in DClass GlesRenderer2\. Is it from a package that the containing file does not import\? Are docs inherited by an un-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name, e\.g\. `@throws java\.io\.IOException under some conditions`\.
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/style\/CurrentUserStyleRepository\.kt:[0-9]+ Unable to find reference @param copySelectedOptions in DClass UserStyle\. Are you trying to refer to something not visible to users\?
-WARN\: \$OUT_DIR\/androidx\/docs\-tip\-of\-tree\/build\/unzippedMultiplatformSources\/nativeMain\/androidx\/lifecycle\/LifecycleRegistry\.native\.kt\:[0-9]+ Link does not resolve for \@throws IllegalStateException in DFunction addObserver\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
+WARN\: \$OUT_DIR\/androidx\/docs\-tip\-of\-tree\/build\/unzippedMultiplatformSources\/nonJvmMain\/androidx\/lifecycle\/LifecycleRegistry\.nonJvm\.kt\:[0-9]+ Link does not resolve for \@throws IllegalStateException in DFunction addObserver\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/CookieManagerCompat\.java:UnknownLine Missing @param tag for parameter `cookieManager` in DFunction getCookieInfo
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setSafeBrowsingEnabled
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setDisabledActionModeMenuItems
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 fa6b521..3d98305 100644
--- a/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
+++ b/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
@@ -53,7 +53,8 @@
     internal val jetbrainsRepositories = listOf(
         "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/",
         "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev",
-        "https://maven.pkg.jetbrains.space/public/p/compose/dev"
+        "https://maven.pkg.jetbrains.space/public/p/compose/dev",
+        "https://maven.pkg.jetbrains.space/kotlin/p/dokka/test"
     )
 
     internal val gradlePluginPortalRepo = "https://plugins.gradle.org/m2/"
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1445138..355f0fa 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -105,7 +105,7 @@
 checkerframework = { module = "org.checkerframework:checker-qual", version = "2.5.3" }
 checkmark = { module = "net.saff.checkmark:checkmark", version = "0.1.6" }
 constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.0.1"}
-dackka = { module = "com.google.devsite:dackka", version = "1.4.3" }
+dackka = { module = "com.google.devsite:dackka", version = "1.5.0" }
 dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
 daggerCompiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
 desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version = "2.0.3" }
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
index d2ceaf3..76efc66 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
@@ -1313,6 +1313,7 @@
                 }
             }
         ) { _, renderer, surfaceView ->
+            commitCount.set(0)
             val latch = CountDownLatch(1)
             commitLatch.set(latch)
             renderer.renderFrontBufferedLayer(Color.RED)
@@ -1321,8 +1322,8 @@
             renderer.renderFrontBufferedLayer(Color.BLUE)
             renderer.commit()
 
+            pendingCommitLatch.set(CountDownLatch(2))
             latch.countDown()
-            pendingCommitLatch.set(CountDownLatch(1))
 
             assertTrue(pendingCommitLatch.get()!!.await(3000, TimeUnit.MILLISECONDS))
 
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
index e8edc46e..6d386fb 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
@@ -35,8 +35,6 @@
 import org.gradle.api.tasks.InputFile
 import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.PathSensitive
-import org.gradle.api.tasks.PathSensitivity
 import org.gradle.api.tasks.TaskAction
 import org.gradle.api.tasks.TaskProvider
 import org.gradle.api.tasks.bundling.Jar
@@ -45,9 +43,8 @@
 
 @CacheableTask
 abstract class DexInspectorTask : DefaultTask() {
-    @get:PathSensitive(PathSensitivity.NONE)
-    @get:InputFile
-    abstract val d8Executable: RegularFileProperty
+    @get:Classpath
+    abstract val d8Executable: ConfigurableFileCollection
 
     @get:Classpath
     @get:InputFile
@@ -76,7 +73,7 @@
         output.parentFile.mkdirs()
         val errorStream = ByteArrayOutputStream()
         val executionResult = execOperations.javaexec {
-            it.classpath(File(File(d8Executable.get().asFile.parentFile, "lib"), "d8.jar"))
+            it.classpath(d8Executable.files)
             it.mainClass.set("com.android.tools.r8.D8")
             it.allJvmArgs.add("-Xmx2G")
 
@@ -112,10 +109,6 @@
         }
     }
 
-    fun setD8(sdkDir: File, toolsVersion: String) {
-        d8Executable.set(File(sdkDir, "build-tools/$toolsVersion/d8"))
-    }
-
     fun setAndroidJar(sdkDir: File, compileSdk: String) {
         // Preview SDK compileSdkVersions are prefixed with "android-", e.g. "android-S".
         val platform = if (compileSdk.startsWith("android")) compileSdk else "android-$compileSdk"
@@ -144,7 +137,9 @@
 
     val dex = tasks.register(variant.taskName("dexInspector"), DexInspectorTask::class.java) {
         it.minSdkVersion = extension.defaultConfig.minSdk!!
-        it.setD8(extension.sdkDirectory, extension.buildToolsVersion)
+        it.d8Executable.setFrom(
+            configurations.detachedConfiguration(dependencies.create("com.android.tools:r8:8.2.47"))
+        )
         it.setAndroidJar(extension.sdkDirectory, extension.compileSdkVersion!!)
         it.jars.from(jar.get().archiveFile)
         it.outputFile.set(output)
diff --git a/leanback/leanback/src/main/res/values-ne/strings.xml b/leanback/leanback/src/main/res/values-ne/strings.xml
index 6e05863..444af5e 100644
--- a/leanback/leanback/src/main/res/values-ne/strings.xml
+++ b/leanback/leanback/src/main/res/values-ne/strings.xml
@@ -54,6 +54,6 @@
     <string name="lb_guidedaction_finish_title" msgid="3330958750346333890">"पूरा गर्नुहोस्"</string>
     <string name="lb_guidedaction_continue_title" msgid="893619591225519922">"जारी राख्नुहोस्"</string>
     <string name="lb_media_player_error" msgid="3228326776757666747">"MediaPlayer को त्रुटिको कोड %1$d, यसको अतिरिक्त %2$d"</string>
-    <string name="lb_onboarding_get_started" msgid="5549711907371161292">"सुरु गरौँ"</string>
+    <string name="lb_onboarding_get_started" msgid="5549711907371161292">"सुरु गर्नुहोस्"</string>
     <string name="lb_onboarding_accessibility_next" msgid="2394451791544864917">"अर्को"</string>
 </resources>
diff --git a/libraryversions.toml b/libraryversions.toml
index 067c73d..1f73d6d 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,6 +1,6 @@
 [versions]
 ACTIVITY = "1.9.0-beta01"
-ANNOTATION = "1.8.0-alpha01"
+ANNOTATION = "1.8.0-alpha02"
 ANNOTATION_EXPERIMENTAL = "1.4.0-rc01"
 APPACTIONS_BUILTINTYPES = "1.0.0-alpha01"
 APPACTIONS_INTERACTION = "1.0.0-alpha01"
@@ -64,7 +64,7 @@
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
 FRAGMENT = "1.7.0-beta01"
-FUTURES = "1.2.0-alpha02"
+FUTURES = "1.2.0-alpha03"
 GLANCE = "1.1.0-alpha01"
 GLANCE_PREVIEW = "1.0.0-alpha06"
 GLANCE_TEMPLATE = "1.0.0-alpha06"
@@ -74,7 +74,7 @@
 GRAPHICS_PATH = "1.0.0-rc01"
 GRAPHICS_SHAPES = "1.0.0-beta01"
 GRIDLAYOUT = "1.1.0-beta02"
-HEALTH_CONNECT = "1.1.0-alpha07"
+HEALTH_CONNECT = "1.1.0-alpha08"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha02"
 HEIFWRITER = "1.1.0-alpha03"
 HILT = "1.2.0-rc01"
diff --git a/lifecycle/lifecycle-common/build.gradle b/lifecycle/lifecycle-common/build.gradle
index a92bda3..322c633 100644
--- a/lifecycle/lifecycle-common/build.gradle
+++ b/lifecycle/lifecycle-common/build.gradle
@@ -24,7 +24,7 @@
 import androidx.build.PlatformIdentifier
 import androidx.build.Publish
 import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
-import org.jetbrains.kotlin.konan.target.Family
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 
 plugins {
     id("AndroidXPlugin")
@@ -53,9 +53,7 @@
             }
         }
 
-        jvmMain {
-            dependsOn(commonMain)
-        }
+        jvmMain.dependsOn(commonMain)
 
         jvmTest {
             dependencies {
@@ -64,33 +62,17 @@
             }
         }
 
-        nativeMain {
+        nonJvmMain {
             dependsOn(commonMain)
             dependencies {
                 implementation(libs.atomicFu)
             }
         }
-        darwinMain {
-            dependsOn(nativeMain)
-        }
-        linuxMain {
-            dependsOn(nativeMain)
-        }
 
         targets.all { target ->
-            if (target.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.native) {
+            if (target.platformType !in [KotlinPlatformType.jvm, KotlinPlatformType.common]) {
                 target.compilations["main"].defaultSourceSet {
-                    def konanTargetFamily = target.konanTarget.family
-                    if (konanTargetFamily == Family.OSX || konanTargetFamily == Family.IOS) {
-                        dependsOn(darwinMain)
-                    } else if (konanTargetFamily == Family.LINUX) {
-                        dependsOn(linuxMain)
-                    } else {
-                        throw new GradleException("unknown native target ${target}")
-                    }
-                }
-                target.compilations["test"].defaultSourceSet {
-                    dependsOn(nativeTest)
+                    dependsOn(nonJvmMain)
                 }
             }
         }
diff --git a/lifecycle/lifecycle-common/src/nativeMain/kotlin/androidx/lifecycle/Lifecycle.native.kt b/lifecycle/lifecycle-common/src/nonJvmMain/kotlin/androidx/lifecycle/Lifecycle.nonJvm.kt
similarity index 92%
rename from lifecycle/lifecycle-common/src/nativeMain/kotlin/androidx/lifecycle/Lifecycle.native.kt
rename to lifecycle/lifecycle-common/src/nonJvmMain/kotlin/androidx/lifecycle/Lifecycle.nonJvm.kt
index f992081..148f8f7 100644
--- a/lifecycle/lifecycle-common/src/nativeMain/kotlin/androidx/lifecycle/Lifecycle.native.kt
+++ b/lifecycle/lifecycle-common/src/nonJvmMain/kotlin/androidx/lifecycle/Lifecycle.nonJvm.kt
@@ -15,9 +15,11 @@
  */
 package androidx.lifecycle
 
+import androidx.annotation.RestrictTo
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CoroutineScope
 
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public actual class AtomicReference<V> actual constructor(value: V) {
     private val delegate = atomic(value)
     public actual fun get(): V = delegate.value
diff --git a/lifecycle/lifecycle-common/src/nativeMain/kotlin/androidx/lifecycle/Lifecycling.native.kt b/lifecycle/lifecycle-common/src/nonJvmMain/kotlin/androidx/lifecycle/Lifecycling.nonJvm.kt
similarity index 100%
rename from lifecycle/lifecycle-common/src/nativeMain/kotlin/androidx/lifecycle/Lifecycling.native.kt
rename to lifecycle/lifecycle-common/src/nonJvmMain/kotlin/androidx/lifecycle/Lifecycling.nonJvm.kt
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index bbbc6a5..f16a328 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -10,7 +10,6 @@
 import androidx.build.Publish
 import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
@@ -86,8 +85,12 @@
             }
         }
 
-        nativeMain {
+        nonJvmMain {
             dependsOn(commonMain)
+        }
+
+        nativeMain {
+            dependsOn(nonJvmMain)
 
             // Required for WeakReference usage
             languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
@@ -96,30 +99,11 @@
         nativeTest {
             dependsOn(commonTest)
         }
-        darwinMain {
-            dependsOn(nativeMain)
-
-            // Required for WeakReference usage
-            languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
-        }
-        linuxMain {
-            dependsOn(nativeMain)
-
-            // Required for WeakReference usage
-            languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
-        }
 
         targets.all { target ->
             if (target.platformType == KotlinPlatformType.native) {
                 target.compilations["main"].defaultSourceSet {
-                    def konanTargetFamily = target.konanTarget.family
-                    if (konanTargetFamily == Family.OSX || konanTargetFamily == Family.IOS) {
-                        dependsOn(darwinMain)
-                    } else if (konanTargetFamily == Family.LINUX) {
-                        dependsOn(linuxMain)
-                    } else {
-                        throw new GradleException("unknown native target ${target}")
-                    }
+                    dependsOn(nativeMain)
 
                     // Required for WeakReference usage
                     languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
diff --git a/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/LifecycleRegistry.native.kt b/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/LifecycleRegistry.native.kt
index 978ee7c..90c6aa6 100644
--- a/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/LifecycleRegistry.native.kt
+++ b/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/LifecycleRegistry.native.kt
@@ -15,342 +15,5 @@
  */
 package androidx.lifecycle
 
-import androidx.annotation.VisibleForTesting
-import kotlin.native.ref.WeakReference
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/**
- * An implementation of [Lifecycle] that can handle multiple observers.
- *
- * It is used by Fragments and Support Library Activities. You can also directly use it if you have
- * a custom LifecycleOwner.
- */
-public actual open class LifecycleRegistry private constructor(
-    provider: LifecycleOwner,
-    private val enforceMainThread: Boolean
-) : Lifecycle() {
-    /**
-     * Invariant: at any moment of time for observer1 & observer2:
-     * if addition_order(observer1) < addition_order(observer2), then
-     * state(observer1) >= state(observer2),
-     */
-    private var observerMap = linkedMapOf<LifecycleObserver, ObserverWithState>()
-
-    /**
-     * Current state
-     */
-    private var state: State = State.INITIALIZED
-
-    /**
-     * The provider that owns this Lifecycle.
-     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
-     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
-     * because it keeps strong references on all other listeners, so you'll leak all of them as
-     * well.
-     */
-    private val lifecycleOwner: WeakReference<LifecycleOwner>
-    private var addingObserverCounter = 0
-    private var handlingEvent = false
-    private var newEventOccurred = false
-
-    // we have to keep it for cases:
-    // void onStart() {
-    //     mRegistry.removeObserver(this);
-    //     mRegistry.add(newObserver);
-    // }
-    // newObserver should be brought only to CREATED state during the execution of
-    // this onStart method. our invariant with observerMap doesn't help, because parent observer
-    // is no longer in the map.
-    private var parentStates = ArrayList<State>()
-
-    /**
-     * Creates a new LifecycleRegistry for the given provider.
-     *
-     * You should usually create this inside your LifecycleOwner class's constructor and hold
-     * onto the same instance.
-     *
-     * @param provider The owner LifecycleOwner
-     */
-    public actual constructor(provider: LifecycleOwner) : this(provider, true)
-
-    init {
-        lifecycleOwner = WeakReference(provider)
-    }
-
-    actual override var currentState: State
-        get() = state
-        /**
-         * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
-         *
-         * @param state new state
-         */
-        set(state) {
-            enforceMainThreadIfNeeded("setCurrentState")
-            moveToState(state)
-        }
-
-    private val _currentStateFlow: MutableStateFlow<State> = MutableStateFlow(State.INITIALIZED)
-    override val currentStateFlow: StateFlow<State>
-        get() = _currentStateFlow.asStateFlow()
-
-    /**
-     * Sets the current state and notifies the observers.
-     *
-     * Note that if the `currentState` is the same state as the last call to this method,
-     * calling this method has no effect.
-     *
-     * @param event The event that was received
-     */
-    public actual open fun handleLifecycleEvent(event: Event) {
-        enforceMainThreadIfNeeded("handleLifecycleEvent")
-        moveToState(event.targetState)
-    }
-
-    private fun moveToState(next: State) {
-        if (state == next) {
-            return
-        }
-        check(!(state == State.INITIALIZED && next == State.DESTROYED)) {
-            "State must be at least CREATED to move to $next, but was $state in component " +
-                "${lifecycleOwner.get()}"
-        }
-        state = next
-        if (handlingEvent || addingObserverCounter != 0) {
-            newEventOccurred = true
-            // we will figure out what to do on upper level.
-            return
-        }
-        handlingEvent = true
-        sync()
-        handlingEvent = false
-        if (state == State.DESTROYED) {
-            observerMap = linkedMapOf()
-        }
-    }
-
-    private val isSynced: Boolean
-        get() {
-            if (observerMap.isEmpty()) {
-                return true
-            }
-            val eldestObserverState = observerMap.values.first().state
-            val newestObserverState = observerMap.values.last().state
-            return eldestObserverState == newestObserverState && state == newestObserverState
-        }
-
-    private fun calculateTargetState(observer: LifecycleObserver): State {
-        val siblingState = observerMap.keys.toList().let {
-            val index = it.indexOf(observer)
-            if (index > 0) observerMap[it[index - 1]]?.state else null
-        }
-        val parentState =
-            if (parentStates.isNotEmpty()) parentStates[parentStates.size - 1] else null
-        return min(min(state, siblingState), parentState)
-    }
-
-    /**
-     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
-     * state.
-     *
-     * The given observer will be brought to the current state of the LifecycleOwner.
-     * For example, if the LifecycleOwner is in [Lifecycle.State.STARTED] state, the given observer
-     * will receive [Lifecycle.Event.ON_CREATE], [Lifecycle.Event.ON_START] events.
-     *
-     * @param observer The observer to notify.
-     *
-     * @throws IllegalStateException if no event up from observer's initial state
-     */
-    override fun addObserver(observer: LifecycleObserver) {
-        enforceMainThreadIfNeeded("addObserver")
-        val initialState = if (state == State.DESTROYED) State.DESTROYED else State.INITIALIZED
-        val statefulObserver = ObserverWithState(observer, initialState)
-        val previous = observerMap.put(observer, statefulObserver)
-        if (previous != null) {
-            return
-        }
-        val lifecycleOwner = lifecycleOwner.get()
-            ?: // it is null we should be destroyed. Fallback quickly
-            return
-        val isReentrance = addingObserverCounter != 0 || handlingEvent
-        var targetState = calculateTargetState(observer)
-        addingObserverCounter++
-        while (statefulObserver.state < targetState && observerMap.contains(observer)) {
-            pushParentState(statefulObserver.state)
-            val event = Event.upFrom(statefulObserver.state)
-                ?: throw IllegalStateException("no event up from ${statefulObserver.state}")
-            statefulObserver.dispatchEvent(lifecycleOwner, event)
-            popParentState()
-            // mState / subling may have been changed recalculate
-            targetState = calculateTargetState(observer)
-        }
-        if (!isReentrance) {
-            // we do sync only on the top level.
-            sync()
-        }
-        addingObserverCounter--
-    }
-
-    private fun popParentState() {
-        parentStates.removeAt(parentStates.size - 1)
-    }
-
-    private fun pushParentState(state: State) {
-        parentStates.add(state)
-    }
-
-    override fun removeObserver(observer: LifecycleObserver) {
-        enforceMainThreadIfNeeded("removeObserver")
-        // we consciously decided not to send destruction events here in opposition to addObserver.
-        // Our reasons for that:
-        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
-        // actually occurred but earlier.
-        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
-        // event. If removeObserver method sends destruction events, then a clean up routine becomes
-        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
-        // a web connection, in the usual routine in OnStop method you report to a server that a
-        // session has just ended and you close the connection. Now let's assume now that you
-        // lost an internet and as a result you removed this observer. If you get destruction
-        // events in removeObserver, you should have a special case in your onStop method that
-        // checks if your web connection died and you shouldn't try to report anything to a server.
-        observerMap.remove(observer)
-    }
-
-    /**
-     * The number of observers.
-     *
-     * @return The number of observers.
-     */
-    public actual open val observerCount: Int
-        get() {
-            enforceMainThreadIfNeeded("getObserverCount")
-            return observerMap.size
-        }
-
-    private fun forwardPass(lifecycleOwner: LifecycleOwner) {
-        forEachObserverWithAdditions { key, observer ->
-            while (observer.state < state && !newEventOccurred && observerMap.contains(key)) {
-                pushParentState(observer.state)
-                val event = Event.upFrom(observer.state)
-                    ?: throw IllegalStateException("no event up from ${observer.state}")
-                observer.dispatchEvent(lifecycleOwner, event)
-                popParentState()
-            }
-        }
-    }
-
-    private fun backwardPass(lifecycleOwner: LifecycleOwner) {
-        forEachObserverReversed { key, observer ->
-            while (observer.state > state && !newEventOccurred && observerMap.contains(key)) {
-                val event = Event.downFrom(observer.state)
-                    ?: throw IllegalStateException("no event down from ${observer.state}")
-                pushParentState(event.targetState)
-                observer.dispatchEvent(lifecycleOwner, event)
-                popParentState()
-            }
-        }
-    }
-
-    private inline fun forEachObserverWithAdditions(
-        block: (LifecycleObserver, ObserverWithState) -> Unit
-    ) {
-        val visited = mutableSetOf<LifecycleObserver>()
-        while (!newEventOccurred) {
-            val keys = observerMap.keys.filter { it !in visited }
-            if (keys.isEmpty()) {
-                break
-            }
-            for (key in keys) {
-                if (newEventOccurred) {
-                    break
-                }
-                val value = observerMap[key] ?: continue
-                block(key, value)
-                visited.add(key)
-            }
-        }
-    }
-
-    private inline fun forEachObserverReversed(
-        block: (LifecycleObserver, ObserverWithState) -> Unit
-    ) {
-        val keys = observerMap.keys.reversed()
-        for (key in keys) {
-            if (newEventOccurred) {
-                break
-            }
-            val value = observerMap[key] ?: continue
-            block(key, value)
-        }
-    }
-
-    // happens only on the top of stack (never in reentrance),
-    // so it doesn't have to take in account parents
-    private fun sync() {
-        val lifecycleOwner = lifecycleOwner.get()
-            ?: throw IllegalStateException(
-                "LifecycleOwner of this LifecycleRegistry is already " +
-                    "garbage collected. It is too late to change lifecycle state."
-            )
-        while (!isSynced) {
-            newEventOccurred = false
-            if (state < observerMap.values.first().state) {
-                backwardPass(lifecycleOwner)
-            }
-            val newest = observerMap.values.lastOrNull()
-            if (!newEventOccurred && newest != null && state > newest.state) {
-                forwardPass(lifecycleOwner)
-            }
-        }
-        newEventOccurred = false
-        _currentStateFlow.value = currentState
-    }
-
-    private fun enforceMainThreadIfNeeded(methodName: String) {
-        if (enforceMainThread) {
-            check(isMainThread()) {
-                ("Method $methodName must be called on the main thread")
-            }
-        }
-    }
-
-    internal class ObserverWithState(observer: LifecycleObserver?, initialState: State) {
-        var state: State
-        private var lifecycleObserver: LifecycleEventObserver
-
-        init {
-            lifecycleObserver = Lifecycling.lifecycleEventObserver(observer!!)
-            state = initialState
-        }
-
-        fun dispatchEvent(owner: LifecycleOwner?, event: Event) {
-            val newState = event.targetState
-            state = min(state, newState)
-            lifecycleObserver.onStateChanged(owner!!, event)
-            state = newState
-        }
-    }
-
-    public actual companion object {
-        /**
-         * Creates a new LifecycleRegistry for the given provider, that doesn't check
-         * that its methods are called on the threads other than main.
-         *
-         * LifecycleRegistry is not synchronized: if multiple threads access this `LifecycleRegistry`, it must be synchronized externally.
-         *
-         * Another possible use-case for this method is JVM testing, when main thread is not present.
-         */
-        @VisibleForTesting
-        public actual fun createUnsafe(owner: LifecycleOwner): LifecycleRegistry {
-            return LifecycleRegistry(owner, false)
-        }
-
-        internal fun min(state1: State, state2: State?): State {
-            return if ((state2 != null) && (state2 < state1)) state2 else state1
-        }
-    }
-}
-
-private fun isMainThread(): Boolean =
+internal actual fun isMainThread(): Boolean =
     MainDispatcherChecker.isMainDispatcherThread()
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt b/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/WeakReference.native.kt
similarity index 64%
rename from wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt
rename to lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/WeakReference.native.kt
index 8f2cad2..9b16c02 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt
+++ b/lifecycle/lifecycle-runtime/src/nativeMain/kotlin/androidx/lifecycle/WeakReference.native.kt
@@ -1,5 +1,5 @@
-package androidx.wear.protolayout.material/*
- * Copyright 2022 The Android Open Source Project
+/*
+ * 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.
@@ -14,4 +14,11 @@
  * limitations under the License.
  */
 
-// This file exists to trick AGP/lint to work around b/234865137
+package androidx.lifecycle
+
+internal actual class WeakReference<T : Any> actual constructor(
+    reference: T
+) {
+    private val kotlinNativeReference = kotlin.native.ref.WeakReference(reference)
+    actual fun get(): T? = kotlinNativeReference.get()
+}
diff --git a/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt b/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt
new file mode 100644
index 0000000..2a7e188
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.lifecycle
+
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * An implementation of [Lifecycle] that can handle multiple observers.
+ *
+ * It is used by Fragments and Support Library Activities. You can also directly use it if you have
+ * a custom LifecycleOwner.
+ */
+public actual open class LifecycleRegistry private constructor(
+    provider: LifecycleOwner,
+    private val enforceMainThread: Boolean
+) : Lifecycle() {
+    /**
+     * Invariant: at any moment of time for observer1 & observer2:
+     * if addition_order(observer1) < addition_order(observer2), then
+     * state(observer1) >= state(observer2),
+     */
+    private var observerMap = linkedMapOf<LifecycleObserver, ObserverWithState>()
+
+    /**
+     * Current state
+     */
+    private var state: State = State.INITIALIZED
+
+    /**
+     * The provider that owns this Lifecycle.
+     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+     * because it keeps strong references on all other listeners, so you'll leak all of them as
+     * well.
+     */
+    private val lifecycleOwner: WeakReference<LifecycleOwner>
+    private var addingObserverCounter = 0
+    private var handlingEvent = false
+    private var newEventOccurred = false
+
+    // we have to keep it for cases:
+    // void onStart() {
+    //     mRegistry.removeObserver(this);
+    //     mRegistry.add(newObserver);
+    // }
+    // newObserver should be brought only to CREATED state during the execution of
+    // this onStart method. our invariant with observerMap doesn't help, because parent observer
+    // is no longer in the map.
+    private var parentStates = ArrayList<State>()
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     *
+     * You should usually create this inside your LifecycleOwner class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider The owner LifecycleOwner
+     */
+    public actual constructor(provider: LifecycleOwner) : this(provider, true)
+
+    init {
+        lifecycleOwner = WeakReference(provider)
+    }
+
+    actual override var currentState: State
+        get() = state
+        /**
+         * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+         *
+         * @param state new state
+         */
+        set(state) {
+            enforceMainThreadIfNeeded("setCurrentState")
+            moveToState(state)
+        }
+
+    private val _currentStateFlow: MutableStateFlow<State> = MutableStateFlow(State.INITIALIZED)
+    override val currentStateFlow: StateFlow<State>
+        get() = _currentStateFlow.asStateFlow()
+
+    /**
+     * Sets the current state and notifies the observers.
+     *
+     * Note that if the `currentState` is the same state as the last call to this method,
+     * calling this method has no effect.
+     *
+     * @param event The event that was received
+     */
+    public actual open fun handleLifecycleEvent(event: Event) {
+        enforceMainThreadIfNeeded("handleLifecycleEvent")
+        moveToState(event.targetState)
+    }
+
+    private fun moveToState(next: State) {
+        if (state == next) {
+            return
+        }
+        check(!(state == State.INITIALIZED && next == State.DESTROYED)) {
+            "State must be at least CREATED to move to $next, but was $state in component " +
+                "${lifecycleOwner.get()}"
+        }
+        state = next
+        if (handlingEvent || addingObserverCounter != 0) {
+            newEventOccurred = true
+            // we will figure out what to do on upper level.
+            return
+        }
+        handlingEvent = true
+        sync()
+        handlingEvent = false
+        if (state == State.DESTROYED) {
+            observerMap = linkedMapOf()
+        }
+    }
+
+    private val isSynced: Boolean
+        get() {
+            if (observerMap.isEmpty()) {
+                return true
+            }
+            val eldestObserverState = observerMap.values.first().state
+            val newestObserverState = observerMap.values.last().state
+            return eldestObserverState == newestObserverState && state == newestObserverState
+        }
+
+    private fun calculateTargetState(observer: LifecycleObserver): State {
+        val siblingState = observerMap.keys.toList().let {
+            val index = it.indexOf(observer)
+            if (index > 0) observerMap[it[index - 1]]?.state else null
+        }
+        val parentState =
+            if (parentStates.isNotEmpty()) parentStates[parentStates.size - 1] else null
+        return min(min(state, siblingState), parentState)
+    }
+
+    /**
+     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
+     * state.
+     *
+     * The given observer will be brought to the current state of the LifecycleOwner.
+     * For example, if the LifecycleOwner is in [Lifecycle.State.STARTED] state, the given observer
+     * will receive [Lifecycle.Event.ON_CREATE], [Lifecycle.Event.ON_START] events.
+     *
+     * @param observer The observer to notify.
+     *
+     * @throws IllegalStateException if no event up from observer's initial state
+     */
+    override fun addObserver(observer: LifecycleObserver) {
+        enforceMainThreadIfNeeded("addObserver")
+        val initialState = if (state == State.DESTROYED) State.DESTROYED else State.INITIALIZED
+        val statefulObserver = ObserverWithState(observer, initialState)
+        val previous = observerMap.put(observer, statefulObserver)
+        if (previous != null) {
+            return
+        }
+        val lifecycleOwner = lifecycleOwner.get()
+            ?: // it is null we should be destroyed. Fallback quickly
+            return
+        val isReentrance = addingObserverCounter != 0 || handlingEvent
+        var targetState = calculateTargetState(observer)
+        addingObserverCounter++
+        while (statefulObserver.state < targetState && observerMap.contains(observer)) {
+            pushParentState(statefulObserver.state)
+            val event = Event.upFrom(statefulObserver.state)
+                ?: throw IllegalStateException("no event up from ${statefulObserver.state}")
+            statefulObserver.dispatchEvent(lifecycleOwner, event)
+            popParentState()
+            // mState / subling may have been changed recalculate
+            targetState = calculateTargetState(observer)
+        }
+        if (!isReentrance) {
+            // we do sync only on the top level.
+            sync()
+        }
+        addingObserverCounter--
+    }
+
+    private fun popParentState() {
+        parentStates.removeAt(parentStates.size - 1)
+    }
+
+    private fun pushParentState(state: State) {
+        parentStates.add(state)
+    }
+
+    override fun removeObserver(observer: LifecycleObserver) {
+        enforceMainThreadIfNeeded("removeObserver")
+        // we consciously decided not to send destruction events here in opposition to addObserver.
+        // Our reasons for that:
+        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
+        // actually occurred but earlier.
+        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
+        // event. If removeObserver method sends destruction events, then a clean up routine becomes
+        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
+        // a web connection, in the usual routine in OnStop method you report to a server that a
+        // session has just ended and you close the connection. Now let's assume now that you
+        // lost an internet and as a result you removed this observer. If you get destruction
+        // events in removeObserver, you should have a special case in your onStop method that
+        // checks if your web connection died and you shouldn't try to report anything to a server.
+        observerMap.remove(observer)
+    }
+
+    /**
+     * The number of observers.
+     *
+     * @return The number of observers.
+     */
+    public actual open val observerCount: Int
+        get() {
+            enforceMainThreadIfNeeded("getObserverCount")
+            return observerMap.size
+        }
+
+    private fun forwardPass(lifecycleOwner: LifecycleOwner) {
+        forEachObserverWithAdditions { key, observer ->
+            while (observer.state < state && !newEventOccurred && observerMap.contains(key)) {
+                pushParentState(observer.state)
+                val event = Event.upFrom(observer.state)
+                    ?: throw IllegalStateException("no event up from ${observer.state}")
+                observer.dispatchEvent(lifecycleOwner, event)
+                popParentState()
+            }
+        }
+    }
+
+    private fun backwardPass(lifecycleOwner: LifecycleOwner) {
+        forEachObserverReversed { key, observer ->
+            while (observer.state > state && !newEventOccurred && observerMap.contains(key)) {
+                val event = Event.downFrom(observer.state)
+                    ?: throw IllegalStateException("no event down from ${observer.state}")
+                pushParentState(event.targetState)
+                observer.dispatchEvent(lifecycleOwner, event)
+                popParentState()
+            }
+        }
+    }
+
+    private inline fun forEachObserverWithAdditions(
+        block: (LifecycleObserver, ObserverWithState) -> Unit
+    ) {
+        val visited = mutableSetOf<LifecycleObserver>()
+        while (!newEventOccurred) {
+            val keys = observerMap.keys.filter { it !in visited }
+            if (keys.isEmpty()) {
+                break
+            }
+            for (key in keys) {
+                if (newEventOccurred) {
+                    break
+                }
+                val value = observerMap[key] ?: continue
+                block(key, value)
+                visited.add(key)
+            }
+        }
+    }
+
+    private inline fun forEachObserverReversed(
+        block: (LifecycleObserver, ObserverWithState) -> Unit
+    ) {
+        val keys = observerMap.keys.reversed()
+        for (key in keys) {
+            if (newEventOccurred) {
+                break
+            }
+            val value = observerMap[key] ?: continue
+            block(key, value)
+        }
+    }
+
+    // happens only on the top of stack (never in reentrance),
+    // so it doesn't have to take in account parents
+    private fun sync() {
+        val lifecycleOwner = lifecycleOwner.get()
+            ?: throw IllegalStateException(
+                "LifecycleOwner of this LifecycleRegistry is already " +
+                    "garbage collected. It is too late to change lifecycle state."
+            )
+        while (!isSynced) {
+            newEventOccurred = false
+            if (state < observerMap.values.first().state) {
+                backwardPass(lifecycleOwner)
+            }
+            val newest = observerMap.values.lastOrNull()
+            if (!newEventOccurred && newest != null && state > newest.state) {
+                forwardPass(lifecycleOwner)
+            }
+        }
+        newEventOccurred = false
+        _currentStateFlow.value = currentState
+    }
+
+    private fun enforceMainThreadIfNeeded(methodName: String) {
+        if (enforceMainThread) {
+            check(isMainThread()) {
+                ("Method $methodName must be called on the main thread")
+            }
+        }
+    }
+
+    internal class ObserverWithState(observer: LifecycleObserver?, initialState: State) {
+        var state: State
+        private var lifecycleObserver: LifecycleEventObserver
+
+        init {
+            lifecycleObserver = Lifecycling.lifecycleEventObserver(observer!!)
+            state = initialState
+        }
+
+        fun dispatchEvent(owner: LifecycleOwner?, event: Event) {
+            val newState = event.targetState
+            state = min(state, newState)
+            lifecycleObserver.onStateChanged(owner!!, event)
+            state = newState
+        }
+    }
+
+    public actual companion object {
+        /**
+         * Creates a new LifecycleRegistry for the given provider, that doesn't check
+         * that its methods are called on the threads other than main.
+         *
+         * LifecycleRegistry is not synchronized: if multiple threads access this
+         * `LifecycleRegistry`, it must be synchronized externally.
+         *
+         * Another possible use-case for this method is JVM testing, when main thread is not present.
+         */
+        @VisibleForTesting
+        public actual fun createUnsafe(owner: LifecycleOwner): LifecycleRegistry {
+            return LifecycleRegistry(owner, false)
+        }
+
+        internal fun min(state1: State, state2: State?): State {
+            return if ((state2 != null) && (state2 < state1)) state2 else state1
+        }
+    }
+}
+
+internal expect fun isMainThread(): Boolean
diff --git a/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/WeakReference.nonJvm.kt b/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/WeakReference.nonJvm.kt
new file mode 100644
index 0000000..62417f8
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/nonJvmMain/kotlin/androidx/lifecycle/WeakReference.nonJvm.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.lifecycle
+
+/**
+ * Class WeakReference encapsulates weak reference to an object, which could be used to either
+ * retrieve a strong reference to an object, or return null, if object was already destroyed by
+ * the memory manager.
+ */
+internal expect class WeakReference<T : Any>(reference: T) {
+    fun get(): T?
+}
diff --git a/paging/paging-common/api/current.txt b/paging/paging-common/api/current.txt
index 92ade99..b356f96 100644
--- a/paging/paging-common/api/current.txt
+++ b/paging/paging-common/api/current.txt
@@ -24,7 +24,7 @@
   }
 
   public final class CombinedLoadStatesKt {
-    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates>);
+    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates?>);
   }
 
   public abstract class DataSource<Key, Value> {
@@ -410,7 +410,7 @@
     method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
     method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
     method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
-    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R>?,?> generator);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R?>,?> generator);
     method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
     method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> transform);
diff --git a/paging/paging-common/api/restricted_current.txt b/paging/paging-common/api/restricted_current.txt
index 92ade99..b356f96 100644
--- a/paging/paging-common/api/restricted_current.txt
+++ b/paging/paging-common/api/restricted_current.txt
@@ -24,7 +24,7 @@
   }
 
   public final class CombinedLoadStatesKt {
-    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates>);
+    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates?>);
   }
 
   public abstract class DataSource<Key, Value> {
@@ -410,7 +410,7 @@
     method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
     method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
     method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
-    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R>?,?> generator);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R?>,?> generator);
     method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
     method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> transform);
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
index 44bcd15..026cb0a 100644
--- a/paging/paging-testing/api/current.txt
+++ b/paging/paging-testing/api/current.txt
@@ -30,12 +30,12 @@
 
   @VisibleForTesting public final class TestPager<Key, Value> {
     ctor public TestPager(androidx.paging.PagingConfig config, androidx.paging.PagingSource<Key,Value> pagingSource);
-    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
-    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>);
+    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>?>);
     method public suspend Object? getPages(kotlin.coroutines.Continuation<java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
     method public suspend Object? getPagingState(int anchorPosition, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
     method public suspend Object? getPagingState(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> anchorPositionLookup, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
-    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
     method public suspend Object? refresh(optional Key? initialKey, kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
   }
 
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
index 44bcd15..026cb0a 100644
--- a/paging/paging-testing/api/restricted_current.txt
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -30,12 +30,12 @@
 
   @VisibleForTesting public final class TestPager<Key, Value> {
     ctor public TestPager(androidx.paging.PagingConfig config, androidx.paging.PagingSource<Key,Value> pagingSource);
-    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
-    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>);
+    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>?>);
     method public suspend Object? getPages(kotlin.coroutines.Continuation<java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
     method public suspend Object? getPagingState(int anchorPosition, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
     method public suspend Object? getPagingState(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> anchorPositionLookup, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
-    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
     method public suspend Object? refresh(optional Key? initialKey, kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
   }
 
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index db5988f..792f76f 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
 # Disable docs
 androidx.enableDocumentation=false
 androidx.playground.snapshotBuildId=11349412
-androidx.playground.metalavaBuildId=11524763
+androidx.playground.metalavaBuildId=11549526
 androidx.studio.type=playground
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/DeleteMe.kt b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/security/security-identity-credential/src/main/java/androidx/security/DeleteMe.kt b/security/security-identity-credential/src/main/java/androidx/security/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/settings.gradle b/settings.gradle
index 0b8888d..b8dd445f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -504,6 +504,7 @@
 includeProject(":compose:foundation:foundation-layout:foundation-layout-samples", "compose/foundation/foundation-layout/samples", [BuildType.COMPOSE])
 includeProject(":compose:foundation:foundation-lint", [BuildType.COMPOSE])
 includeProject(":compose:foundation:foundation:integration-tests:foundation-demos", [BuildType.COMPOSE])
+includeProject(":compose:foundation:foundation:integration-tests:lazy-tests", [BuildType.COMPOSE])
 includeProject(":compose:foundation:foundation:foundation-samples", "compose/foundation/foundation/samples", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests:demos", [BuildType.COMPOSE])
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
index 78fa81c..94bb1db 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
@@ -25,8 +25,10 @@
 import androidx.fragment.app.FragmentManager
 import androidx.fragment.app.FragmentTransaction
 import androidx.fragment.app.TargetTracking
+import androidx.test.core.app.ActivityScenario
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.runOnUiThreadRethrow
+import androidx.testutils.withActivity
 import androidx.transition.test.R
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -51,6 +53,12 @@
     return ret
 }
 
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.executePendingTransactions(
+    fm: FragmentManager = withActivity { supportFragmentManager }
+) {
+    onActivity { fm.executePendingTransactions() }
+}
+
 @Suppress("DEPRECATION")
 fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.popBackStackImmediate(): Boolean {
     val instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
index a2445a9..1d3f0bf 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
@@ -33,7 +33,6 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,7 +48,6 @@
         FragmentTransitionTestActivity::class.java
     )
 
-    @Ignore // b/324309532
     @Test
     fun replaceOperationWithTransitionsThenGestureBack() {
         val fm1 = activityRule.activity.supportFragmentManager
@@ -126,78 +124,83 @@
 
     @Test
     fun replaceOperationWithTransitionsThenBackCancelled() {
-        val fm1 = activityRule.activity.supportFragmentManager
-
-        var startedEnter = false
-        val fragment1 = TransitionFragment(R.layout.scene1)
-        fragment1.setReenterTransition(Fade().apply {
-            duration = 300
-            addListener(object : TransitionListenerAdapter() {
-                override fun onTransitionStart(transition: Transition) {
-                    startedEnter = true
-                }
+        withUse(ActivityScenario.launch(FragmentTransitionTestActivity::class.java)) {
+            val fm1 = withActivity {
+                supportFragmentManager
+            }
+            var startedEnter = false
+            val fragment1 = TransitionFragment(R.layout.scene1)
+            fragment1.setReenterTransition(Fade().apply {
+                duration = 300
+                addListener(object : TransitionListenerAdapter() {
+                    override fun onTransitionStart(transition: Transition) {
+                        startedEnter = true
+                    }
+                })
             })
-        })
 
-        fm1.beginTransaction()
-            .replace(R.id.fragmentContainer, fragment1, "1")
-            .setReorderingAllowed(true)
-            .addToBackStack(null)
-            .commit()
-        activityRule.waitForExecution()
+            fm1.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment1, "1")
+                .setReorderingAllowed(true)
+                .addToBackStack(null)
+                .commit()
+            waitForExecution()
 
-        val startedExitCountDownLatch = CountDownLatch(1)
-        val fragment2 = TransitionFragment()
-        fragment2.setReturnTransition(Fade().apply {
-            duration = 300
-            addListener(object : TransitionListenerAdapter() {
-                override fun onTransitionStart(transition: Transition) {
-                    startedExitCountDownLatch.countDown()
-                }
+            val startedExitCountDownLatch = CountDownLatch(1)
+            val fragment2 = TransitionFragment()
+            fragment2.setReturnTransition(Fade().apply {
+                duration = 300
+                addListener(object : TransitionListenerAdapter() {
+                    override fun onTransitionStart(transition: Transition) {
+                        startedExitCountDownLatch.countDown()
+                    }
+                })
             })
-        })
 
-        fm1.beginTransaction()
-            .replace(R.id.fragmentContainer, fragment2, "2")
-            .setReorderingAllowed(true)
-            .addToBackStack(null)
-            .commit()
-        activityRule.executePendingTransactions()
+            fm1.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2, "2")
+                .setReorderingAllowed(true)
+                .addToBackStack(null)
+                .commit()
+            executePendingTransactions()
 
-        fragment1.waitForTransition()
-        fragment2.waitForTransition()
+            fragment1.waitForTransition()
+            fragment2.waitForTransition()
 
-        val dispatcher = activityRule.activity.onBackPressedDispatcher
-        activityRule.runOnUiThread {
-            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+            val dispatcher = activityRule.activity.onBackPressedDispatcher
+            activityRule.runOnUiThread {
+                dispatcher.dispatchOnBackStarted(
+                    BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT)
+                )
+            }
+            executePendingTransactions(fm1)
+
+            activityRule.runOnUiThread {
+                dispatcher.dispatchOnBackProgressed(
+                    BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+                )
+            }
+            executePendingTransactions(fm1)
+
+            assertThat(startedEnter).isTrue()
+            assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+            activityRule.runOnUiThread {
+                dispatcher.dispatchOnBackCancelled()
+            }
+            executePendingTransactions(fm1)
+
+            // The executePendingTransaction will end the transition so we should not wait here.
+            fragment1.waitForNoTransition()
+
+            assertThat(fragment2.isAdded).isTrue()
+            assertThat(fm1.findFragmentByTag("2")).isEqualTo(fragment2)
+
+            // Make sure the original fragment was correctly readded to the container
+            assertThat(fragment2.requireView()).isNotNull()
         }
-        activityRule.executePendingTransactions(fm1)
-
-        activityRule.runOnUiThread {
-            dispatcher.dispatchOnBackProgressed(
-                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
-            )
-        }
-        activityRule.executePendingTransactions(fm1)
-
-        assertThat(startedEnter).isTrue()
-        assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
-
-        activityRule.runOnUiThread {
-            dispatcher.dispatchOnBackCancelled()
-        }
-        activityRule.executePendingTransactions(fm1)
-
-        fragment1.waitForTransition()
-
-        assertThat(fragment2.isAdded).isTrue()
-        assertThat(fm1.findFragmentByTag("2")).isEqualTo(fragment2)
-
-        // Make sure the original fragment was correctly readded to the container
-        assertThat(fragment2.requireView()).isNotNull()
     }
 
-    @Ignore // b/324309532
     @Test
     fun replaceOperationWithTransitionsThenGestureBackTwice() {
         val fm1 = activityRule.activity.supportFragmentManager
@@ -339,7 +342,6 @@
         assertThat(fragment1.requireView().parent).isNotNull()
     }
 
-    @Ignore // b/300694860
     @Test
     fun replaceOperationWithTransitionsThenOnBackPressedTwice() {
         val fm1 = activityRule.activity.supportFragmentManager
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSupportTest.java b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSupportTest.java
index 4a76dc8..91efb1c 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSupportTest.java
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSupportTest.java
@@ -41,7 +41,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.transition.test.R;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -64,7 +63,6 @@
         mReorderingAllowed = reorderingAllowed;
     }
 
-    @Ignore // b/326237469
     @Test
     public void preconditions() {
         final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
@@ -79,7 +77,6 @@
         assertNotNull(fragment2.mBlue);
     }
 
-    @Ignore // b/326237469
     @Test
     public void nonSharedTransition() {
         final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
@@ -101,7 +98,6 @@
                 .onTransitionStart(any(Transition.class));
     }
 
-    @Ignore // b/326237469
     @Test
     public void sharedTransition() {
         final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
index d5c1329..678b18e 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
@@ -36,7 +36,6 @@
 import org.junit.After
 import org.junit.Assert.fail
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -77,7 +76,6 @@
         fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
     }
 
-    @Ignore // b/326237469
     // Test that normal view transitions (enter, exit, reenter, return) run with
     // a single fragment.
     @Test
@@ -122,7 +120,6 @@
         assertThat(onBackStackChangedTimes).isEqualTo(4)
     }
 
-    @Ignore // b/326237469
     // Test removing a Fragment with a Transition and adding it back before the Transition
     // finishes is handled correctly.
     @Test
@@ -159,7 +156,6 @@
         verifyNoOtherTransitions(fragment)
     }
 
-    @Ignore // b/326237469
     @Test
     fun testTimedPostponeImmediateStartNotCanceled() {
         val fm = activityRule.activity.supportFragmentManager
@@ -198,7 +194,6 @@
         assertThat(cancelCount).isEqualTo(0)
     }
 
-    @Ignore // b/326237469
     @Test
     fun ensureTransitionsFinishBeforeViewDestroyed() {
         // enter transition
@@ -235,7 +230,6 @@
         assertThat(fragment.transitionCountInOnDestroyView).isEqualTo(0)
     }
 
-    @Ignore // b/326237469
     // Test that shared elements transition from one fragment to the next
     // and back during pop.
     @Test
@@ -251,7 +245,6 @@
         verifyPopTransition(1, fragment2, fragment1)
     }
 
-    @Ignore // b/326237469
     @Test
     fun sharedElementNoOtherTransition() {
         val fragment1 = setupInitialFragment()
@@ -293,7 +286,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     @Test
     fun sharedElementAddNoOtherTransition() {
         val fragment1 = setupInitialFragment()
@@ -328,7 +320,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     // Test that shared elements transition from one fragment to the next
     // and back during pop.
     @Suppress("DEPRECATION")
@@ -346,7 +337,6 @@
         verifyPopTransition(1, fragment2, fragment1)
     }
 
-    @Ignore // b/326237469
     // Test that shared element transitions through multiple fragments work together
     @Test
     fun intermediateFragment() {
@@ -364,7 +354,6 @@
         verifyPopTransition(2, fragment3, fragment1, fragment2)
     }
 
-    @Ignore // b/326237469
     // Adding/removing the same fragment multiple times shouldn't mess anything up
     @Test
     fun removeAdded() {
@@ -418,7 +407,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     // Make sure that shared elements on two different fragment containers don't interact
     @Test
     fun crossContainer() {
@@ -458,7 +446,6 @@
         verifyCrossTransition(true, fragment1, fragment2)
     }
 
-    @Ignore // b/326237469
     // Make sure that onSharedElementStart and onSharedElementEnd are called
     @Suppress("UNCHECKED_CAST")
     @Test
@@ -519,7 +506,6 @@
         assertThat(snapshots.value).isNull()
     }
 
-    @Ignore // b/326237469
     // Make sure that onMapSharedElement works to change the shared element going out
     @Test
     fun onMapSharedElementOut() {
@@ -592,7 +578,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // Make sure that onMapSharedElement works to change the shared element target
     @Test
     fun onMapSharedElementIn() {
@@ -664,7 +649,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // Ensure that shared element transitions that have targets properly target the views
     @Test
     fun complexSharedElementTransition() {
@@ -727,7 +711,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // Ensure that after transitions have executed that they don't have any targets or other
     // unfortunate modifications.
     @Test
@@ -756,7 +739,6 @@
         assertThat(fragment2.reenterTransition.epicenterCallback).isNull()
     }
 
-    @Ignore // b/326237469
     // Ensure that transitions are done when a fragment is shown and hidden
     @Test
     fun showHideTransition() {
@@ -825,7 +807,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     // Test that setting allowEnterTransitionOverlap to false correctly delays
     // the enter transition until after the exit transition finishes
     @Test
@@ -877,7 +858,6 @@
             .isFalse()
     }
 
-    @Ignore // b/326237469
     // Ensure that transitions are done when a fragment is attached and detached
     @Test
     fun attachDetachTransition() {
@@ -927,7 +907,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     // Ensure that shared element without matching transition name doesn't error out
     @Test
     fun sharedElementMismatch() {
@@ -964,7 +943,6 @@
         verifyNoOtherTransitions(fragment2)
     }
 
-    @Ignore // b/326237469
     // Ensure that using the same source or target shared element results in an exception.
     @Test
     fun sharedDuplicateTargetNames() {
@@ -998,7 +976,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // Test that invisible fragment views don't participate in transitions
     @Test
     fun invisibleNoTransitions() {
@@ -1037,7 +1014,6 @@
         verifyNoOtherTransitions(fragment)
     }
 
-    @Ignore // b/326237469
     // No crash when transitioning a shared element and there is no shared element transition.
     @Test
     fun noSharedElementTransition() {
@@ -1120,7 +1096,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // No crash when there is no shared element transition and transitioning a shared element after
     // a pop
     @Test
@@ -1185,7 +1160,6 @@
         }
     }
 
-    @Ignore // b/326237469
     // When there is no matching shared element, the transition name should not be changed
     @Test
     fun noMatchingSharedElementRetainName() {
@@ -1233,7 +1207,6 @@
         assertThat(endGreen.transitionName).isEqualTo("greenSquare")
     }
 
-    @Ignore // b/326237469
     @Test
     fun ignoreWhenViewNotAttached() {
         with(ActivityScenario.launch(AddTransitionFragmentInActivity::class.java)) {
diff --git a/transition/transition/src/main/java/androidx/transition/DeleteMe.kt b/transition/transition/src/main/java/androidx/transition/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/transition/transition/src/main/java/androidx/transition/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/tv/tv-foundation/build.gradle b/tv/tv-foundation/build.gradle
index bb1ae47..69b4909 100644
--- a/tv/tv-foundation/build.gradle
+++ b/tv/tv-foundation/build.gradle
@@ -39,15 +39,16 @@
 
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
-    api("androidx.annotation:annotation:1.6.0")
-    api("androidx.compose.animation:animation:1.6.0")
-    api("androidx.compose.foundation:foundation:1.6.0")
-    api("androidx.compose.foundation:foundation-layout:1.6.0")
-    api("androidx.compose.runtime:runtime:1.6.0")
-    api("androidx.compose.ui:ui-util:1.6.0")
-    api("androidx.compose.ui:ui:1.6.0")
-    api("androidx.compose.ui:ui-graphics:1.6.0")
-    api("androidx.compose.ui:ui-text:1.6.0")
+    def composeVersion = "1.6.3"
+    api("androidx.annotation:annotation:$composeVersion")
+    api("androidx.compose.animation:animation:$composeVersion")
+    api("androidx.compose.foundation:foundation:$composeVersion")
+    api("androidx.compose.foundation:foundation-layout:$composeVersion")
+    api("androidx.compose.runtime:runtime:$composeVersion")
+    api("androidx.compose.ui:ui-util:$composeVersion")
+    api("androidx.compose.ui:ui:$composeVersion")
+    api("androidx.compose.ui:ui-graphics:$composeVersion")
+    api("androidx.compose.ui:ui-text:$composeVersion")
 
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":compose:runtime:runtime"))
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 6bdb7e9..db20405 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -76,13 +76,13 @@
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonShape {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardBorder {
+  @androidx.compose.runtime.Immutable public final class CardBorder {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardColors {
+  @androidx.compose.runtime.Immutable public final class CardColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardDefaults {
+  public final class CardDefaults {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardColors compactCardColors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor);
@@ -97,31 +97,31 @@
     field public static final float VerticalImageAspectRatio = 0.6666667f;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardGlow {
+  @androidx.compose.runtime.Immutable public final class CardGlow {
   }
 
   public final class CardKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void CompactCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.ui.graphics.Brush scrimBrush, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void ClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void CompactCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.ui.graphics.Brush scrimBrush, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void WideClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardLayoutColors {
+  @androidx.compose.runtime.Immutable public final class CardLayoutColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardLayoutDefaults {
+  public final class CardLayoutDefaults {
     method @androidx.compose.runtime.Composable public void ImageCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardLayoutColors contentColor(optional long contentColor, optional long focusedContentColor, optional long pressedContentColor);
     field public static final androidx.tv.material3.CardLayoutDefaults INSTANCE;
   }
 
   public final class CardLayoutKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void StandardCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @androidx.compose.runtime.Composable public static void StandardCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @androidx.compose.runtime.Composable public static void WideCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardScale {
+  @androidx.compose.runtime.Immutable public final class CardScale {
     field public static final androidx.tv.material3.CardScale.Companion Companion;
   }
 
@@ -130,7 +130,7 @@
     property public final androidx.tv.material3.CardScale None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardShape {
+  @androidx.compose.runtime.Immutable public final class CardShape {
   }
 
   @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselDefaults {
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 6bdb7e9..db20405 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -76,13 +76,13 @@
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonShape {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardBorder {
+  @androidx.compose.runtime.Immutable public final class CardBorder {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardColors {
+  @androidx.compose.runtime.Immutable public final class CardColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardDefaults {
+  public final class CardDefaults {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardColors compactCardColors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor);
@@ -97,31 +97,31 @@
     field public static final float VerticalImageAspectRatio = 0.6666667f;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardGlow {
+  @androidx.compose.runtime.Immutable public final class CardGlow {
   }
 
   public final class CardKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void CompactCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.ui.graphics.Brush scrimBrush, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void ClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void CompactCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.ui.graphics.Brush scrimBrush, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @androidx.compose.runtime.Composable public static void WideClassicCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> image, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardLayoutColors {
+  @androidx.compose.runtime.Immutable public final class CardLayoutColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardLayoutDefaults {
+  public final class CardLayoutDefaults {
     method @androidx.compose.runtime.Composable public void ImageCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional androidx.tv.material3.CardShape shape, optional androidx.tv.material3.CardColors colors, optional androidx.tv.material3.CardScale scale, optional androidx.tv.material3.CardBorder border, optional androidx.tv.material3.CardGlow glow, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.CardLayoutColors contentColor(optional long contentColor, optional long focusedContentColor, optional long pressedContentColor);
     field public static final androidx.tv.material3.CardLayoutDefaults INSTANCE;
   }
 
   public final class CardLayoutKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void StandardCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @androidx.compose.runtime.Composable public static void StandardCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @androidx.compose.runtime.Composable public static void WideCardLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Unit> imageCard, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> description, optional androidx.tv.material3.CardLayoutColors contentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardScale {
+  @androidx.compose.runtime.Immutable public final class CardScale {
     field public static final androidx.tv.material3.CardScale.Companion Companion;
   }
 
@@ -130,7 +130,7 @@
     property public final androidx.tv.material3.CardScale None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardShape {
+  @androidx.compose.runtime.Immutable public final class CardShape {
   }
 
   @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselDefaults {
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index 258c875..a53a48d 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -32,9 +32,13 @@
 
 dependencies {
     api(libs.kotlinStdlib)
-    api("androidx.compose.animation:animation:1.5.3")
-    api(project(":compose:foundation:foundation"))
-    api("androidx.compose.material:material-icons-core:1.5.3")
+
+    def composeVersion = "1.6.3"
+    api("androidx.compose.animation:animation:$composeVersion")
+    api("androidx.compose.foundation:foundation:$composeVersion")
+    api("androidx.compose.foundation:foundation-layout:$composeVersion")
+    api("androidx.compose.material:material-icons-core:$composeVersion")
+
     api(project(":tv:tv-foundation"))
 
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Card.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Card.kt
index dab410f..0465e8a 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Card.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Card.kt
@@ -66,7 +66,6 @@
  * still happen internally.
  * @param content defines the [Composable] content inside the Card.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun Card(
     onClick: () -> Unit,
@@ -129,7 +128,6 @@
  * or preview the card in different states. Note that if `null` is provided, interactions will
  * still happen internally.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun ClassicCard(
     onClick: () -> Unit,
@@ -212,7 +210,6 @@
  * or preview the card in different states. Note that if `null` is provided, interactions will
  * still happen internally.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun CompactCard(
     onClick: () -> Unit,
@@ -298,7 +295,6 @@
  * or preview the card in different states. Note that if `null` is provided, interactions will
  * still happen internally.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun WideClassicCard(
     onClick: () -> Unit,
@@ -369,7 +365,6 @@
 /**
  * Contains the default values used by all card types.
  */
-@ExperimentalTvMaterial3Api
 object CardDefaults {
     internal val ContentImageAlignment = Alignment.Center
 
@@ -547,7 +542,6 @@
 private const val SubtitleAlpha = 0.6f
 private const val DescriptionAlpha = 0.8f
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 private fun CardColors.toClickableSurfaceColors() =
     ClickableSurfaceColors(
         containerColor = containerColor,
@@ -560,7 +554,6 @@
         disabledContentColor = contentColor
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 private fun CardShape.toClickableSurfaceShape() =
     ClickableSurfaceShape(
         shape = shape,
@@ -570,7 +563,6 @@
         focusedDisabledShape = shape
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 private fun CardScale.toClickableSurfaceScale() =
     ClickableSurfaceScale(
         scale = scale,
@@ -580,7 +572,6 @@
         focusedDisabledScale = scale
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 private fun CardBorder.toClickableSurfaceBorder() =
     ClickableSurfaceBorder(
         border = border,
@@ -590,7 +581,6 @@
         focusedDisabledBorder = border
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 private fun CardGlow.toClickableSurfaceGlow() =
     ClickableSurfaceGlow(
         glow = glow,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/CardLayout.kt b/tv/tv-material/src/main/java/androidx/tv/material3/CardLayout.kt
index c4273ac..85521bb 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/CardLayout.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/CardLayout.kt
@@ -55,7 +55,6 @@
  * [Interaction]s for this CardLayout.
  * This interaction source param would also be forwarded to be used with the `imageCard` composable.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun StandardCardLayout(
     imageCard: @Composable (interactionSource: MutableInteractionSource) -> Unit,
@@ -117,7 +116,6 @@
  * [Interaction]s for this CardLayout.
  * This interaction source param would also be forwarded to be used with the `imageCard` composable.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun WideCardLayout(
     imageCard: @Composable (interactionSource: MutableInteractionSource) -> Unit,
@@ -165,7 +163,6 @@
     }
 }
 
-@ExperimentalTvMaterial3Api
 object CardLayoutDefaults {
     /**
      * Creates [CardLayoutColors] that represents the default content colors used in a
@@ -243,7 +240,6 @@
 /**
  * Represents the [Color] of content in a CardLayout for different interaction states.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardLayoutColors internal constructor(
     internal val contentColor: Color,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/CardStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/CardStyles.kt
index 6856416..6bc81e6 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/CardStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/CardStyles.kt
@@ -24,7 +24,6 @@
 /**
  * Represents the [Color] of Card in different interaction states.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardColors internal constructor(
     internal val containerColor: Color,
@@ -75,7 +74,6 @@
 /**
  * Represents the [Shape] of Card in different interaction states.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardShape internal constructor(
     internal val shape: Shape,
@@ -112,7 +110,6 @@
  * Represents the scaleFactor of Card in different interaction states.
  * Note: This scaleFactor must always be a non-negative float.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardScale internal constructor(
     @FloatRange(from = 0.0) internal val scale: Float,
@@ -159,7 +156,6 @@
 /**
  * Represents the [Border] of Card in different interaction states.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardBorder internal constructor(
     internal val border: Border,
@@ -196,7 +192,6 @@
 /**
  * Represents the [Glow] of Card in different interaction states.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class CardGlow internal constructor(
     internal val glow: Glow,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Checkbox.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Checkbox.kt
index d642e52..338b752 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Checkbox.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Checkbox.kt
@@ -146,7 +146,8 @@
                 onClick = onClick,
                 enabled = enabled,
                 role = Role.Checkbox,
-                interactionSource = interactionSource,
+                // TODO: remove the optional argument once we update to compose 1.7.x
+                interactionSource = interactionSource ?: remember { MutableInteractionSource() },
                 indication = null
             )
         } else {
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/RadioButton.kt b/tv/tv-material/src/main/java/androidx/tv/material3/RadioButton.kt
index c3c3285..97477e2 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/RadioButton.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/RadioButton.kt
@@ -29,6 +29,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -85,7 +86,8 @@
                 onClick = onClick,
                 enabled = enabled,
                 role = Role.RadioButton,
-                interactionSource = interactionSource,
+                // TODO: remove the optional argument once we update to compose 1.7.x
+                interactionSource = interactionSource ?: remember { MutableInteractionSource() },
                 indication = null
             )
         } else {
diff --git a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/DeleteMe.kt b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 5784324..324e62f 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
     defaultConfig {
         applicationId "androidx.wear.compose.integration.demos"
         minSdk 25
-        versionCode 19
-        versionName "1.19"
+        versionCode 21
+        versionName "1.21"
     }
 
     buildTypes {
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/DeleteMe.kt b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/wear/watchface/watchface-complications-data-source-ktx/api/current.txt b/wear/watchface/watchface-complications-data-source-ktx/api/current.txt
index 55d30d5..621eb48 100644
--- a/wear/watchface/watchface-complications-data-source-ktx/api/current.txt
+++ b/wear/watchface/watchface-complications-data-source-ktx/api/current.txt
@@ -4,13 +4,13 @@
   public abstract class SuspendingComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService {
     ctor public SuspendingComplicationDataSourceService();
     method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener);
-    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData>);
+    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData?>);
   }
 
   public abstract class SuspendingTimelineComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService {
     ctor public SuspendingTimelineComplicationDataSourceService();
     method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener);
-    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.datasource.ComplicationDataTimeline>);
+    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.datasource.ComplicationDataTimeline?>);
   }
 
 }
diff --git a/wear/watchface/watchface-complications-data-source-ktx/api/restricted_current.txt b/wear/watchface/watchface-complications-data-source-ktx/api/restricted_current.txt
index 55d30d5..621eb48 100644
--- a/wear/watchface/watchface-complications-data-source-ktx/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data-source-ktx/api/restricted_current.txt
@@ -4,13 +4,13 @@
   public abstract class SuspendingComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService {
     ctor public SuspendingComplicationDataSourceService();
     method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener);
-    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData>);
+    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData?>);
   }
 
   public abstract class SuspendingTimelineComplicationDataSourceService extends androidx.wear.watchface.complications.datasource.ComplicationDataSourceService {
     ctor public SuspendingTimelineComplicationDataSourceService();
     method public final void onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener listener);
-    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.datasource.ComplicationDataTimeline>);
+    method @UiThread public abstract suspend Object? onComplicationRequest(androidx.wear.watchface.complications.datasource.ComplicationRequest request, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.datasource.ComplicationDataTimeline?>);
   }
 
 }
diff --git a/wear/watchface/watchface-complications/api/current.txt b/wear/watchface/watchface-complications/api/current.txt
index 5bbb240..46dbdf8 100644
--- a/wear/watchface/watchface-complications/api/current.txt
+++ b/wear/watchface/watchface-complications/api/current.txt
@@ -20,8 +20,8 @@
   public final class ComplicationDataSourceInfoRetriever implements java.lang.AutoCloseable {
     ctor public ComplicationDataSourceInfoRetriever(android.content.Context context);
     method public void close();
-    method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.Result[]>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
-    method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
+    method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.Result[]?>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
+    method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData?>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
   }
 
   public static final class ComplicationDataSourceInfoRetriever.Result {
diff --git a/wear/watchface/watchface-complications/api/restricted_current.txt b/wear/watchface/watchface-complications/api/restricted_current.txt
index 5bbb240..46dbdf8 100644
--- a/wear/watchface/watchface-complications/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications/api/restricted_current.txt
@@ -20,8 +20,8 @@
   public final class ComplicationDataSourceInfoRetriever implements java.lang.AutoCloseable {
     ctor public ComplicationDataSourceInfoRetriever(android.content.Context context);
     method public void close();
-    method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.Result[]>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
-    method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
+    method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveComplicationDataSourceInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.Result[]?>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
+    method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName complicationDataSourceComponent, androidx.wear.watchface.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.watchface.complications.data.ComplicationData?>) throws androidx.wear.watchface.complications.ComplicationDataSourceInfoRetriever.ServiceDisconnectedException;
   }
 
   public static final class ComplicationDataSourceInfoRetriever.Result {
diff --git a/wear/watchface/watchface-editor-guava/api/current.txt b/wear/watchface/watchface-editor-guava/api/current.txt
index 1230171..6a33f4c 100644
--- a/wear/watchface/watchface-editor-guava/api/current.txt
+++ b/wear/watchface/watchface-editor-guava/api/current.txt
@@ -18,7 +18,7 @@
     method public boolean isCommitChangesOnClose();
     method @UiThread public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.editor.ListenableEditorSession?> listenableCreateOnWatchEditorSession(androidx.activity.ComponentActivity activity);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.editor.ChosenComplicationDataSource?> listenableOpenComplicationDataSourceChooser(int complicationSlotId);
-    method public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource>);
+    method public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource?>);
     method public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
     method public void setCommitChangesOnClose(boolean);
     property public Integer? backgroundComplicationSlotId;
diff --git a/wear/watchface/watchface-editor-guava/api/restricted_current.txt b/wear/watchface/watchface-editor-guava/api/restricted_current.txt
index 1230171..6a33f4c 100644
--- a/wear/watchface/watchface-editor-guava/api/restricted_current.txt
+++ b/wear/watchface/watchface-editor-guava/api/restricted_current.txt
@@ -18,7 +18,7 @@
     method public boolean isCommitChangesOnClose();
     method @UiThread public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.editor.ListenableEditorSession?> listenableCreateOnWatchEditorSession(androidx.activity.ComponentActivity activity);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.editor.ChosenComplicationDataSource?> listenableOpenComplicationDataSourceChooser(int complicationSlotId);
-    method public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource>);
+    method public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource?>);
     method public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
     method public void setCommitChangesOnClose(boolean);
     property public Integer? backgroundComplicationSlotId;
diff --git a/wear/watchface/watchface-editor/api/current.txt b/wear/watchface/watchface-editor/api/current.txt
index 10e482e..7386097 100644
--- a/wear/watchface/watchface-editor/api/current.txt
+++ b/wear/watchface/watchface-editor/api/current.txt
@@ -48,7 +48,7 @@
     method public android.content.ComponentName getWatchFaceComponentName();
     method public androidx.wear.watchface.client.WatchFaceId getWatchFaceId();
     method @UiThread public boolean isCommitChangesOnClose();
-    method @UiThread public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource>);
+    method @UiThread public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource?>);
     method @UiThread public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
     method @UiThread public void setCommitChangesOnClose(boolean);
     property public abstract Integer? backgroundComplicationSlotId;
diff --git a/wear/watchface/watchface-editor/api/restricted_current.txt b/wear/watchface/watchface-editor/api/restricted_current.txt
index 10e482e..7386097 100644
--- a/wear/watchface/watchface-editor/api/restricted_current.txt
+++ b/wear/watchface/watchface-editor/api/restricted_current.txt
@@ -48,7 +48,7 @@
     method public android.content.ComponentName getWatchFaceComponentName();
     method public androidx.wear.watchface.client.WatchFaceId getWatchFaceId();
     method @UiThread public boolean isCommitChangesOnClose();
-    method @UiThread public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource>);
+    method @UiThread public suspend Object? openComplicationDataSourceChooser(int complicationSlotId, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationDataSource?>);
     method @UiThread public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
     method @UiThread public void setCommitChangesOnClose(boolean);
     property public abstract Integer? backgroundComplicationSlotId;
diff --git a/wear/wear/src/main/java/androidx/wear/DeleteMe.kt b/wear/wear/src/main/java/androidx/wear/DeleteMe.kt
deleted file mode 100644
index 38f8b7a..0000000
--- a/wear/wear/src/main/java/androidx/wear/DeleteMe.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2022 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 exists to trick AGP/lint to work around b/234865137
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteWorkManagerClientTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteWorkManagerClientTest.kt
index 8af59c5..d11a796 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteWorkManagerClientTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteWorkManagerClientTest.kt
@@ -137,7 +137,7 @@
     @Test
     @MediumTest
     @Suppress("UNCHECKED_CAST")
-    public fun cleanUpWhenSessionIsInvalid() {
+    public fun executeWhenSessionIsInvalid() {
         if (Build.VERSION.SDK_INT <= 27) {
             // Exclude <= API 27, from tests because it causes a SIGSEGV.
             return
@@ -154,7 +154,7 @@
             exception = throwable
         }
         assertNotNull(exception)
-        verify(mClient).cleanUp()
+        assertTrue(exception!!.message!!.contains("Something bad happened"))
         verify(mRunnableScheduler, atLeastOnce())
             .scheduleWithDelay(anyLong(), any(Runnable::class.java))
     }
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
index a8ba626..121fbfd 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
@@ -16,21 +16,19 @@
 
 package androidx.work.multiprocess;
 
-import static android.content.Context.BIND_AUTO_CREATE;
+import static androidx.work.multiprocess.ServiceBindingKt.bindToService;
 
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.IBinder;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.work.Logger;
-import androidx.work.impl.utils.futures.SettableFuture;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -52,7 +50,7 @@
     final Executor mExecutor;
 
     private final Object mLock;
-    private Connection mConnection;
+    private Session<IListenableWorkerImpl> mConnection;
 
     public ListenableWorkerImplClient(
             @NonNull Context context,
@@ -73,22 +71,14 @@
         synchronized (mLock) {
             if (mConnection == null) {
                 Logger.get().debug(TAG,
-                        "Binding to " + component.getPackageName() + ", " + component.getClassName());
-
-                mConnection = new Connection();
-                try {
-                    Intent intent = new Intent();
-                    intent.setComponent(component);
-                    boolean bound = mContext.bindService(intent, mConnection, BIND_AUTO_CREATE);
-                    if (!bound) {
-                        unableToBind(mConnection,
-                                new RuntimeException("Unable to bind to service"));
-                    }
-                } catch (Throwable throwable) {
-                    unableToBind(mConnection, throwable);
-                }
+                        "Binding to " + component.getPackageName() + ", "
+                                + component.getClassName());
+                Intent intent = new Intent();
+                intent.setComponent(component);
+                mConnection = bindToService(mContext, intent,
+                        IListenableWorkerImpl.Stub::asInterface, TAG);
             }
-            return mConnection.mFuture;
+            return mConnection.getConnectedFuture();
         }
     }
 
@@ -134,56 +124,7 @@
      */
     @Nullable
     @VisibleForTesting
-    public Connection getConnection() {
+    Session<IListenableWorkerImpl> getConnection() {
         return mConnection;
     }
-
-    /**
-     * The implementation of {@link ServiceConnection} that handles changes in the connection.
-     *
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static class Connection implements ServiceConnection {
-        private static final String TAG = Logger.tagWithPrefix("ListenableWorkerImplSession");
-
-        final SettableFuture<IListenableWorkerImpl> mFuture;
-
-        public Connection() {
-            mFuture = SettableFuture.create();
-        }
-
-        @Override
-        public void onServiceConnected(
-                @NonNull ComponentName componentName,
-                @NonNull IBinder iBinder) {
-            Logger.get().debug(TAG, "Service connected");
-            IListenableWorkerImpl iListenableWorkerImpl =
-                    IListenableWorkerImpl.Stub.asInterface(iBinder);
-            mFuture.set(iListenableWorkerImpl);
-        }
-
-        @Override
-        public void onServiceDisconnected(@NonNull ComponentName componentName) {
-            Logger.get().warning(TAG, "Service disconnected");
-            mFuture.setException(new RuntimeException("Service disconnected"));
-        }
-
-        @Override
-        public void onBindingDied(@NonNull ComponentName name) {
-            Logger.get().warning(TAG, "Binding died");
-            mFuture.setException(new RuntimeException("Binding died"));
-        }
-
-        @Override
-        public void onNullBinding(@NonNull ComponentName name) {
-            Logger.get().error(TAG, "Unable to bind to service");
-            mFuture.setException(
-                    new RuntimeException("Cannot bind to service " + name));
-        }
-    }
-
-    private static void unableToBind(@NonNull Connection session, @NonNull Throwable throwable) {
-        Logger.get().error(TAG, "Unable to bind to service", throwable);
-        session.mFuture.setException(throwable);
-    }
 }
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
index 818566c..e70baee 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
@@ -16,17 +16,12 @@
 
 package androidx.work.multiprocess;
 
-import static android.content.Context.BIND_AUTO_CREATE;
-
 import static androidx.work.multiprocess.RemoteClientUtils.map;
 import static androidx.work.multiprocess.RemoteClientUtils.sVoidMapper;
 
 import android.annotation.SuppressLint;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
 import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
@@ -35,6 +30,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.arch.core.util.Function;
 import androidx.work.Data;
+import androidx.work.DirectExecutor;
 import androidx.work.ExistingPeriodicWorkPolicy;
 import androidx.work.ExistingWorkPolicy;
 import androidx.work.ForegroundInfo;
@@ -48,7 +44,6 @@
 import androidx.work.WorkRequest;
 import androidx.work.impl.WorkContinuationImpl;
 import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.utils.futures.SettableFuture;
 import androidx.work.multiprocess.parcelable.ParcelConverters;
 import androidx.work.multiprocess.parcelable.ParcelableForegroundRequestInfo;
 import androidx.work.multiprocess.parcelable.ParcelableUpdateRequest;
@@ -63,7 +58,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
 /**
@@ -81,7 +75,7 @@
     static final String TAG = Logger.tagWithPrefix("RemoteWorkManagerClient");
 
     // Synthetic access
-    Session mSession;
+    Session<IWorkManagerImpl> mSession;
 
     final Context mContext;
     final WorkManagerImpl mWorkManager;
@@ -341,7 +335,7 @@
      * @return The current {@link Session} in use by {@link RemoteWorkManagerClient}.
      */
     @Nullable
-    public Session getCurrentSession() {
+    Session<IWorkManagerImpl> getCurrentSession() {
         return mSession;
     }
 
@@ -381,13 +375,6 @@
     ListenableFuture<byte[]> execute(
             @NonNull final ListenableFuture<IWorkManagerImpl> session,
             @NonNull final RemoteDispatcher<IWorkManagerImpl> dispatcher) {
-        session.addListener(() -> {
-            try {
-                session.get();
-            } catch (ExecutionException | InterruptedException exception) {
-                cleanUp();
-            }
-        }, mExecutor);
         ListenableFuture<byte[]> future = RemoteExecuteKt.execute(mExecutor, session,
                 dispatcher);
         future.addListener(() -> {
@@ -405,21 +392,21 @@
     ListenableFuture<IWorkManagerImpl> getSession(@NonNull Intent intent) {
         synchronized (mLock) {
             mSessionIndex += 1;
+            ListenableFuture<IWorkManagerImpl> resultFuture;
             if (mSession == null) {
-                Logger.get().debug(TAG, "Creating a new session");
-                mSession = new Session(this);
-                try {
-                    boolean bound = mContext.bindService(intent, mSession, BIND_AUTO_CREATE);
-                    if (!bound) {
-                        unableToBind(mSession, new RuntimeException("Unable to bind to service"));
-                    }
-                } catch (Throwable throwable) {
-                    unableToBind(mSession, throwable);
-                }
+                mSession = ServiceBindingKt.bindToService(mContext, intent,
+                        IWorkManagerImpl.Stub::asInterface, TAG);
+                // reading future right away, because `this::cleanUp` will synchronously
+                // set mSession to null.
+                resultFuture = mSession.getConnectedFuture();
+                mSession.getDisconnectedFuture()
+                        .addListener(this::cleanUp, DirectExecutor.INSTANCE);
+            } else {
+                resultFuture = mSession.getConnectedFuture();
             }
             // Reset session tracker.
             mRunnableScheduler.cancel(mSessionTracker);
-            return mSession.mFuture;
+            return resultFuture;
         }
     }
 
@@ -434,11 +421,6 @@
         }
     }
 
-    private void unableToBind(@NonNull Session session, @NonNull Throwable throwable) {
-        Logger.get().error(TAG, "Unable to bind to service", throwable);
-        session.mFuture.setException(throwable);
-    }
-
     /**
      * @return the intent that is used to bind to the instance of {@link IWorkManagerImpl}.
      */
@@ -447,59 +429,6 @@
     }
 
     /**
-     * The implementation of {@link ServiceConnection} that handles changes in the connection.
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static class Session implements ServiceConnection {
-        private static final String TAG = Logger.tagWithPrefix("RemoteWMgr.Connection");
-
-        final SettableFuture<IWorkManagerImpl> mFuture;
-        final RemoteWorkManagerClient mClient;
-
-        public Session(@NonNull RemoteWorkManagerClient client) {
-            mClient = client;
-            mFuture = SettableFuture.create();
-        }
-
-        @Override
-        public void onServiceConnected(
-                @NonNull ComponentName componentName,
-                @NonNull IBinder iBinder) {
-            Logger.get().debug(TAG, "Service connected");
-            IWorkManagerImpl iWorkManagerImpl = IWorkManagerImpl.Stub.asInterface(iBinder);
-            mFuture.set(iWorkManagerImpl);
-        }
-
-        @Override
-        public void onServiceDisconnected(@NonNull ComponentName componentName) {
-            Logger.get().debug(TAG, "Service disconnected");
-            mFuture.setException(new RuntimeException("Service disconnected"));
-            mClient.cleanUp();
-        }
-
-        @Override
-        public void onBindingDied(@NonNull ComponentName name) {
-            onBindingDied();
-        }
-
-        /**
-         * Clean-up client when a binding dies.
-         */
-        public void onBindingDied() {
-            Logger.get().debug(TAG, "Binding died");
-            mFuture.setException(new RuntimeException("Binding died"));
-            mClient.cleanUp();
-        }
-
-        @Override
-        public void onNullBinding(@NonNull ComponentName name) {
-            Logger.get().error(TAG, "Unable to bind to service");
-            mFuture.setException(
-                    new RuntimeException("Cannot bind to service " + name));
-        }
-    }
-
-    /**
      * A {@link Runnable} that enforces a TTL for a {@link RemoteWorkManagerClient} session.
      */
     public static class SessionTracker implements Runnable {
@@ -515,7 +444,7 @@
             final long preLockIndex = mClient.getSessionIndex();
             synchronized (mClient.getSessionLock()) {
                 final long sessionIndex = mClient.getSessionIndex();
-                final Session currentSession = mClient.getCurrentSession();
+                final Session<IWorkManagerImpl> currentSession = mClient.getCurrentSession();
                 // We check for a session index here. This is because if the index changes
                 // while we acquire a lock, that would mean that a new session request came through.
                 if (currentSession != null) {
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt
new file mode 100644
index 0000000..870d0d3
--- /dev/null
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.work.multiprocess
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.IInterface
+import androidx.concurrent.futures.SuspendToFutureAdapter.launchFuture
+import androidx.core.util.Function
+import androidx.work.Logger
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
+
+internal fun <T : IInterface?> bindToService(
+    context: Context,
+    intent: Intent,
+    asInterface: Function<IBinder?, T>,
+    loggingTag: String
+): Session<T> {
+    Logger.get().debug(loggingTag, "Binding via $intent")
+
+    val session = Session(loggingTag, asInterface)
+    try {
+        val bound = context.bindService(intent, session, Context.BIND_AUTO_CREATE)
+        if (!bound) {
+            session.resolveClosedConnection(RuntimeException("Unable to bind to service"))
+        }
+    } catch (throwable: Throwable) {
+        session.resolveClosedConnection(throwable)
+    }
+    return session
+}
+
+internal class Session<T : IInterface?>(
+    private val logTag: String,
+    private val asInterface: Function<IBinder?, T>
+) : ServiceConnection {
+
+    sealed class State {
+        object Created : State()
+        class Connected(val iBinder: IBinder) : State()
+        class Disconnected(val throwable: Throwable) : State()
+    }
+
+    private val stateFlow = MutableStateFlow<State>(State.Created)
+
+    val connectedFuture = launchFuture<T>(Dispatchers.Unconfined) {
+        val state = stateFlow.first { it != State.Created }
+        if (state is State.Connected) {
+            asInterface.apply(state.iBinder)
+        } else {
+            // we can go straight to disconnected state if we failed to bind
+            throw (state as State.Disconnected).throwable
+        }
+    }
+
+    val disconnectedFuture = launchFuture<Unit> {
+        stateFlow.first { it is State.Disconnected }
+    }
+
+    override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
+        stateFlow.value = State.Connected(iBinder)
+    }
+
+    override fun onServiceDisconnected(componentName: ComponentName) {
+        Logger.get().debug(logTag, "Service disconnected")
+        resolveClosedConnection(RuntimeException("Service disconnected"))
+    }
+
+    override fun onBindingDied(name: ComponentName) {
+        onBindingDied()
+    }
+
+    /**
+     * Clean-up client when a binding dies.
+     */
+    fun onBindingDied() {
+        Logger.get().debug(logTag, "Binding died")
+        resolveClosedConnection(RuntimeException("Binding died"))
+    }
+
+    override fun onNullBinding(name: ComponentName) {
+        Logger.get().error(logTag, "Unable to bind to service")
+        resolveClosedConnection(RuntimeException("Cannot bind to service $name"))
+    }
+
+    fun resolveClosedConnection(throwable: Throwable) {
+        stateFlow.value = State.Disconnected(throwable)
+    }
+}
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 59abcf4..b681410 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -155,14 +155,14 @@
     method public float[]? getFloatArray(String key);
     method public int getInt(String key, int defaultValue);
     method public int[]? getIntArray(String key);
-    method public java.util.Map<java.lang.String,java.lang.Object> getKeyValueMap();
+    method public java.util.Map<java.lang.String,java.lang.Object?> getKeyValueMap();
     method public long getLong(String key, long defaultValue);
     method public long[]? getLongArray(String key);
     method public String? getString(String key);
     method public String[]? getStringArray(String key);
     method public <T> boolean hasKeyWithValueOfType(String key, Class<T> klass);
     method public byte[] toByteArray();
-    property public final java.util.Map<java.lang.String,java.lang.Object> keyValueMap;
+    property public final java.util.Map<java.lang.String,java.lang.Object?> keyValueMap;
     field public static final androidx.work.Data.Companion Companion;
     field public static final androidx.work.Data EMPTY;
     field public static final int MAX_DATA_BYTES = 10240; // 0x2800
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 59abcf4..b681410 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -155,14 +155,14 @@
     method public float[]? getFloatArray(String key);
     method public int getInt(String key, int defaultValue);
     method public int[]? getIntArray(String key);
-    method public java.util.Map<java.lang.String,java.lang.Object> getKeyValueMap();
+    method public java.util.Map<java.lang.String,java.lang.Object?> getKeyValueMap();
     method public long getLong(String key, long defaultValue);
     method public long[]? getLongArray(String key);
     method public String? getString(String key);
     method public String[]? getStringArray(String key);
     method public <T> boolean hasKeyWithValueOfType(String key, Class<T> klass);
     method public byte[] toByteArray();
-    property public final java.util.Map<java.lang.String,java.lang.Object> keyValueMap;
+    property public final java.util.Map<java.lang.String,java.lang.Object?> keyValueMap;
     field public static final androidx.work.Data.Companion Companion;
     field public static final androidx.work.Data EMPTY;
     field public static final int MAX_DATA_BYTES = 10240; // 0x2800
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
index 8b9be1d..eb0b885 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
@@ -86,7 +86,8 @@
             .build()
         val scheduler = mock(Scheduler::class.java)
         workDatabase = WorkDatabase.create(
-            context, taskExecutor.serialTaskExecutor, config.clock, true)
+            context, taskExecutor.serialTaskExecutor, config.clock, true
+        )
         processor = spy(Processor(context, config, taskExecutor, workDatabase))
         workManager = spy(
             WorkManagerImpl(
@@ -100,11 +101,13 @@
         )
         workDatabase = workManager.workDatabase
         // Initialize WorkConstraintsTracker
-        tracker = WorkConstraintsTracker(Trackers(
-            context = context,
-            taskExecutor = taskExecutor,
-            batteryChargingTracker = fakeChargingTracker
-        ))
+        tracker = WorkConstraintsTracker(
+            Trackers(
+                context = context,
+                taskExecutor = taskExecutor,
+                batteryChargingTracker = fakeChargingTracker
+            )
+        )
         // Initialize dispatcher
         dispatcherCallback = mock(SystemForegroundDispatcher.Callback::class.java)
         dispatcher = spy(SystemForegroundDispatcher(context, workManager, tracker))
@@ -118,8 +121,10 @@
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
         workDatabase.workSpecDao().insertWorkSpec(request.workSpec)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.stringId, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.stringId, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .startForeground(eq(notificationId), eq(0), any<Notification>())
@@ -136,8 +141,10 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.stringId, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.stringId, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .startForeground(eq(notificationId), eq(0), any<Notification>())
@@ -172,6 +179,8 @@
         val metadata = ForegroundInfo(notificationId, notification)
         val intent = createNotifyIntent(context, workSpecId, metadata)
         dispatcher.mCurrentForegroundId = WorkGenerationalId("anotherWorkSpecId", 0)
+        dispatcher.mForegroundInfoById[dispatcher.mCurrentForegroundId] =
+            ForegroundInfo(10, mock(Notification::class.java))
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .notify(eq(notificationId), any<Notification>())
@@ -385,14 +394,18 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.stringId, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.stringId, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         assertThat(fakeChargingTracker.isTracking, `is`(true))
         fakeChargingTracker.state = false
-        verify(workManager, times(1)).stopForegroundWork(eq(
-            WorkGenerationalId(request.workSpec.id, 0)
-        ))
+        verify(workManager, times(1)).stopForegroundWork(
+            eq(
+                WorkGenerationalId(request.workSpec.id, 0)
+            )
+        )
     }
 
     @Test
@@ -406,8 +419,10 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.workSpec.id, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.workSpec.id, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         assertThat(fakeChargingTracker.isTracking, `is`(true))
         val stopIntent = createCancelWorkIntent(context, request.stringId)
@@ -426,8 +441,10 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.stringId, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.stringId, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         processor.stopWork(StartStopToken(WorkGenerationalId(request.stringId, 0)), 0)
         val state = workDatabase.workSpecDao().getState(request.stringId)
@@ -452,8 +469,10 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context,
-            WorkGenerationalId(request.stringId, 0), metadata)
+        val intent = createStartForegroundIntent(
+            context,
+            WorkGenerationalId(request.stringId, 0), metadata
+        )
         dispatcher.onStartCommand(intent)
         assertThat(fakeChargingTracker.isTracking, `is`(true))
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
index 99422cc..de9b1e8 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
@@ -210,6 +210,7 @@
         mCallback = callback;
     }
 
+    @MainThread
     void onStartCommand(@NonNull Intent intent) {
         String action = intent.getAction();
         if (ACTION_START_FOREGROUND.equals(action)) {
@@ -264,6 +265,9 @@
     @SuppressWarnings("deprecation")
     @MainThread
     private void handleNotify(@NonNull Intent intent) {
+        if (mCallback == null) {
+            throw new IllegalStateException("handleNotify was called on the destroyed dispatcher");
+        }
         int notificationId = intent.getIntExtra(KEY_NOTIFICATION_ID, 0);
         int notificationType = intent.getIntExtra(KEY_FOREGROUND_SERVICE_TYPE, 0);
         String workSpecId = intent.getStringExtra(KEY_WORKSPEC_ID);
@@ -275,42 +279,41 @@
                 "Notifying with (id:" + notificationId
                         + ", workSpecId: " + workSpecId
                         + ", notificationType :" + notificationType + ")");
+        if (notification == null) {
+            throw new IllegalArgumentException("Notification passed in the intent was null.");
+        }
 
-        if (notification != null && mCallback != null) {
-            // Keep track of this ForegroundInfo
-            ForegroundInfo info = new ForegroundInfo(
-                    notificationId, notification, notificationType);
-
-            mForegroundInfoById.put(workId, info);
-            if (mCurrentForegroundId == null) {
-                // This is the current workSpecId which owns the Foreground lifecycle.
-                mCurrentForegroundId = workId;
-                mCallback.startForeground(notificationId, notificationType, notification);
-            } else {
-                // Update notification
-                mCallback.notify(notificationId, notification);
-                // Update the notification in the foreground such that it's the union of
-                // all current foreground service types if necessary.
-                if (notificationType != FOREGROUND_SERVICE_TYPE_NONE
-                        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                    int foregroundServiceType = FOREGROUND_SERVICE_TYPE_NONE;
-                    for (Map.Entry<WorkGenerationalId, ForegroundInfo> entry
-                            : mForegroundInfoById.entrySet()) {
-                        ForegroundInfo foregroundInfo = entry.getValue();
-                        foregroundServiceType |= foregroundInfo.getForegroundServiceType();
-                    }
-                    ForegroundInfo currentInfo =
-                            mForegroundInfoById.get(mCurrentForegroundId);
-                    if (currentInfo != null) {
-                        mCallback.startForeground(
-                                currentInfo.getNotificationId(),
-                                foregroundServiceType,
-                                currentInfo.getNotification()
-                        );
-                    }
+        // Keep track of this ForegroundInfo
+        ForegroundInfo info = new ForegroundInfo(notificationId, notification, notificationType);
+        mForegroundInfoById.put(workId, info);
+        ForegroundInfo currentInfo = mForegroundInfoById.get(mCurrentForegroundId);
+        ForegroundInfo resultInfo;
+        if (currentInfo == null) {
+            // This is the current workSpecId which owns the Foreground lifecycle.
+            mCurrentForegroundId = workId;
+            resultInfo = info;
+        } else {
+            // Update notification
+            mCallback.notify(notificationId, notification);
+            // Update the notification in the foreground such that it's the union of
+            // all current foreground service types if necessary.
+            // Before Q startForeground didn't receive foregroundServiceType, so no need to
+            // calculate it.
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                int foregroundServiceType = FOREGROUND_SERVICE_TYPE_NONE;
+                for (Map.Entry<WorkGenerationalId, ForegroundInfo> entry
+                        : mForegroundInfoById.entrySet()) {
+                    ForegroundInfo foregroundInfo = entry.getValue();
+                    foregroundServiceType |= foregroundInfo.getForegroundServiceType();
                 }
+                resultInfo = new ForegroundInfo(currentInfo.getNotificationId(),
+                        currentInfo.getNotification(), foregroundServiceType);
+            } else {
+                resultInfo = currentInfo;
             }
         }
+        mCallback.startForeground(resultInfo.getNotificationId(),
+                resultInfo.getForegroundServiceType(), resultInfo.getNotification());
     }
 
     @MainThread
@@ -428,6 +431,7 @@
          * An implementation of this callback should call
          * {@link android.app.Service#startForeground(int, Notification, int)}.
          */
+        @MainThread
         void startForeground(
                 int notificationId,
                 int notificationType,
@@ -436,16 +440,19 @@
         /**
          * Used to update the {@link Notification}.
          */
+        @MainThread
         void notify(int notificationId, @NonNull Notification notification);
 
         /**
          * Used to cancel a {@link Notification}.
          */
+        @MainThread
         void cancelNotification(int notificationId);
 
         /**
          * Used to stop the {@link SystemForegroundService}.
          */
+        @MainThread
         void stop();
     }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
index cad168e..62efc86 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
@@ -24,8 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 
 import androidx.annotation.DoNotInline;
 import androidx.annotation.MainThread;
@@ -48,7 +46,6 @@
     @Nullable
     private static SystemForegroundService sForegroundService = null;
 
-    private Handler mHandler;
     private boolean mIsShutdown;
 
     // Synthetic access
@@ -94,14 +91,12 @@
 
     @MainThread
     private void initializeDispatcher() {
-        mHandler = new Handler(Looper.getMainLooper());
         mNotificationManager = (NotificationManager)
                 getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
         mDispatcher = new SystemForegroundDispatcher(getApplicationContext());
         mDispatcher.setCallback(this);
     }
 
-    @SuppressWarnings("deprecation")
     @MainThread
     @Override
     public void stop() {
@@ -116,47 +111,34 @@
         stopSelf();
     }
 
+    @MainThread
     @Override
     public void startForeground(
             final int notificationId,
             final int notificationType,
             @NonNull final Notification notification) {
-
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                    Api31Impl.startForeground(SystemForegroundService.this, notificationId,
-                            notification, notificationType);
-                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                    Api29Impl.startForeground(SystemForegroundService.this, notificationId,
-                            notification, notificationType);
-                } else {
-                    startForeground(notificationId, notification);
-                }
-            }
-        });
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            Api31Impl.startForeground(SystemForegroundService.this, notificationId,
+                    notification, notificationType);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            Api29Impl.startForeground(SystemForegroundService.this, notificationId,
+                    notification, notificationType);
+        } else {
+            startForeground(notificationId, notification);
+        }
     }
 
+    @MainThread
     @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
     @Override
     public void notify(final int notificationId, @NonNull final Notification notification) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mNotificationManager.notify(notificationId, notification);
-            }
-        });
+        mNotificationManager.notify(notificationId, notification);
     }
 
     @Override
+    @MainThread
     public void cancelNotification(final int notificationId) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mNotificationManager.cancel(notificationId);
-            }
-        });
+        mNotificationManager.cancel(notificationId);
     }
 
     /**