Merge "Use dackka for coordinatorlayout docs" into androidx-main
diff --git a/.github/ci-control/ci-config.json b/.github/ci-control/ci-config.json
index 4423f47..ea425a6 100644
--- a/.github/ci-control/ci-config.json
+++ b/.github/ci-control/ci-config.json
@@ -13,7 +13,6 @@
     },
     "main" : {
         "exclude" : [
-            "lifecycle"
         ],
         "default": true
     }
diff --git a/appcompat/appcompat-lint/integration-tests/build.gradle b/appcompat/appcompat-lint/integration-tests/build.gradle
index 0ed3410..13246f8 100644
--- a/appcompat/appcompat-lint/integration-tests/build.gradle
+++ b/appcompat/appcompat-lint/integration-tests/build.gradle
@@ -6,7 +6,7 @@
 
 dependencies {
     implementation(project(":appcompat:appcompat"))
-    implementation(project(":core:core"))
+    implementation(projectOrArtifact(":core:core"))
     api(libs.kotlinStdlib)
 }
 
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 81f3292..6c342ce 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -18,7 +18,7 @@
 
     // Required to make activity 1.5.0-rc01 dependencies resolve.
     implementation("androidx.core:core-ktx:1.8.0")
-    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.0")
+    implementation(libs.kotlinStdlib)
 
     implementation(projectOrArtifact(":emoji2:emoji2"))
     implementation(projectOrArtifact(":emoji2:emoji2-views-helper"))
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
index b8c582d..07064be 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
@@ -118,7 +118,9 @@
 
     private Resources getResourcesInternal() {
         if (mResources == null) {
-            if (isEmptyConfiguration(mOverrideConfiguration)) {
+            if (mOverrideConfiguration == null
+                    || (Build.VERSION.SDK_INT >= 26
+                    && isEmptyConfiguration(mOverrideConfiguration))) {
                 // If we're not applying any overrides, use the base context's resources. On API
                 // 26+, this will avoid pulling in resources that share a backing implementation
                 // with the application context.
@@ -215,6 +217,7 @@
      * @return {@code true} if the specified configuration is {@code null} or is a no-op when
      *         used as a configuration overlay
      */
+    @RequiresApi(26)
     private static boolean isEmptyConfiguration(Configuration overrideConfiguration) {
         if (overrideConfiguration == null) {
             return true;
diff --git a/bluetooth/bluetooth/build.gradle b/bluetooth/bluetooth/build.gradle
index 9bf971b..a83aefa 100644
--- a/bluetooth/bluetooth/build.gradle
+++ b/bluetooth/bluetooth/build.gradle
@@ -24,6 +24,7 @@
 
 dependencies {
     implementation(libs.kotlinStdlib)
+    implementation 'androidx.annotation:annotation:1.4.0'
 }
 
 androidx {
@@ -36,4 +37,7 @@
 
 android {
     namespace "androidx.bluetooth"
+    defaultConfig {
+        minSdkVersion 21
+    }
 }
diff --git a/bluetooth/bluetooth/src/main/AndroidManifest.xml b/bluetooth/bluetooth/src/main/AndroidManifest.xml
index 6abff90..24c3903 100644
--- a/bluetooth/bluetooth/src/main/AndroidManifest.xml
+++ b/bluetooth/bluetooth/src/main/AndroidManifest.xml
@@ -13,4 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
\ No newline at end of file
+
+
+    
+
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt
new file mode 100644
index 0000000..2aaf3a5
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.bluetooth
+
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.bluetooth.utils.Bundleable
+
+import java.util.UUID
+
+/**
+ * @hide
+ */
+class BluetoothGattDescriptor internal constructor(
+    descriptor: android.bluetooth.BluetoothGattDescriptor
+) : Bundleable {
+    private val impl: GattDescriptorImpl =
+        if (Build.VERSION.SDK_INT >= 24) {
+            GattDescriptorImplApi24(descriptor)
+        } else {
+            GattDescriptorImplApi21(descriptor)
+        }
+    internal val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor
+        get() = impl.fwkDescriptor
+    val permissions: Int
+        get() = impl.permissions
+    val uuid: UUID
+        get() = impl.uuid
+    val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+        get() = impl.characteristic
+
+    constructor(uuid: UUID, permissions: Int) : this(
+        android.bluetooth.BluetoothGattDescriptor(
+            uuid,
+            permissions
+        )
+    )
+
+    companion object {
+        const val PERMISSION_READ = android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ
+        const val PERMISSION_READ_ENCRYPTED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED
+        const val PERMISSION_READ_ENCRYPTED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM
+        const val PERMISSION_WRITE = android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE
+        const val PERMISSION_WRITE_ENCRYPTED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED
+        const val PERMISSION_WRITE_ENCRYPTED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM
+        const val PERMISSION_WRITE_SIGNED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED
+        const val PERMISSION_WRITE_SIGNED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM
+
+        val ENABLE_NOTIFICATION_VALUE = byteArrayOf(0x01, 0x00)
+        val ENABLE_INDICATION_VALUE = byteArrayOf(0x02, 0x00)
+        val DISABLE_NOTIFICATION_VALUE = byteArrayOf(0x00, 0x00)
+
+        internal fun keyForField(field: Int): String? {
+            return field.toString(Character.MAX_RADIX)
+        }
+
+        val CREATOR: Bundleable.Creator =
+            if (Build.VERSION.SDK_INT >= 24) GattDescriptorImplApi24.CREATOR
+            else GattDescriptorImplApi21.CREATOR
+    }
+
+    override fun toBundle(): Bundle {
+        return impl.toBundle()
+    }
+
+    private interface GattDescriptorImpl {
+        val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor
+        val permissions: Int
+        val uuid: UUID
+        val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+        fun toBundle(): Bundle
+    }
+
+    private open class GattDescriptorImplApi21(
+        descriptor: android.bluetooth.BluetoothGattDescriptor
+    ) : GattDescriptorImpl {
+        companion object {
+
+            internal const val FIELD_FWK_DESCRIPTOR_PERMISSIONS = 1
+            internal const val FIELD_FWK_DESCRIPTOR_UUID = 2
+            internal const val FIELD_FWK_DESCRIPTOR_INSTANCE = 3
+
+            val CREATOR: Bundleable.Creator =
+                object : Bundleable.Creator {
+
+                    @Suppress("DEPRECATION")
+                    override fun fromBundle(bundle: Bundle): BluetoothGattDescriptor {
+                        val permissions =
+                            bundle.getInt(
+                                keyForField(FIELD_FWK_DESCRIPTOR_PERMISSIONS),
+                                -1
+                            )
+                        val uuid = bundle.getString(
+                            keyForField(FIELD_FWK_DESCRIPTOR_UUID),
+                        ) ?: throw IllegalArgumentException("Bundle doesn't include uuid")
+
+                        if (permissions == -1) {
+                            throw IllegalArgumentException("Bundle doesn't include permission")
+                        }
+
+                        val descriptor =
+                            android.bluetooth.BluetoothGattDescriptor(
+                                UUID.fromString(uuid),
+                                permissions
+                            )
+
+                        descriptor.javaClass.getDeclaredField("mInstance").setInt(
+                            descriptor, bundle.getInt(
+                                keyForField(FIELD_FWK_DESCRIPTOR_INSTANCE), 0
+                            )
+                        )
+                        return BluetoothGattDescriptor(descriptor)
+                    }
+                }
+        }
+
+        override val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor = descriptor
+        override val permissions: Int
+            get() = fwkDescriptor.permissions
+        override val uuid: UUID
+            get() = fwkDescriptor.uuid
+        override val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+            get() = fwkDescriptor.characteristic
+
+        override fun toBundle(): Bundle {
+            val bundle = Bundle()
+            bundle.putString(keyForField(FIELD_FWK_DESCRIPTOR_UUID), uuid.toString())
+            bundle.putInt(keyForField(FIELD_FWK_DESCRIPTOR_PERMISSIONS), permissions)
+            val instanceId: Int =
+                fwkDescriptor.javaClass.getDeclaredField("mInstance").getInt(fwkDescriptor)
+            bundle.putInt(keyForField(FIELD_FWK_DESCRIPTOR_INSTANCE), instanceId)
+            return bundle
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.N)
+    private open class GattDescriptorImplApi24(
+        descriptor: android.bluetooth.BluetoothGattDescriptor
+    ) : GattDescriptorImplApi21(descriptor) {
+        companion object {
+            internal const val FIELD_FWK_DESCRIPTOR = 0
+            val CREATOR: Bundleable.Creator =
+                object : Bundleable.Creator {
+                    @Suppress("DEPRECATION")
+                    override fun fromBundle(bundle: Bundle): BluetoothGattDescriptor {
+                        val fwkDescriptor =
+                            bundle.getParcelable(
+                                keyForField(FIELD_FWK_DESCRIPTOR)
+                            ) ?: throw IllegalArgumentException("Bundle doesn't contain descriptor")
+                        return BluetoothGattDescriptor(fwkDescriptor)
+                    }
+                }
+        }
+
+        override fun toBundle(): Bundle {
+            val bundle = Bundle()
+            bundle.putParcelable(
+                keyForField(FIELD_FWK_DESCRIPTOR),
+                fwkDescriptor
+            )
+            return bundle
+        }
+    }
+}
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt
new file mode 100644
index 0000000..73440dd
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.bluetooth.utils
+
+import android.os.Bundle
+
+/**
+ * @hide
+ */
+interface Bundleable {
+    /** Returns a [Bundle] representing the information stored in this object.  */
+    fun toBundle(): Bundle
+
+    /** Interface for the static `CREATOR` field of [Bundleable] classes.  */
+    interface Creator {
+        /**
+         * Restores a [Bundleable] instance from a [Bundle] produced by [ ][Bundleable.toBundle].
+         *
+         *
+         * It guarantees the compatibility of [Bundle] representations produced by different
+         * versions of [Bundleable.toBundle] by providing best default values for missing
+         * fields. It throws an exception if any essential fields are missing.
+         */
+        fun fromBundle(bundle: Bundle): T
+    }
+}
\ No newline at end of file
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXPluginTestContext.kt b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXPluginTestContext.kt
index 94dd541..35c08d3 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXPluginTestContext.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXPluginTestContext.kt
@@ -165,16 +165,21 @@
     fun AndroidXSelfTestProject.readPublishedFile(fileName: String) =
         mavenLocalDir.resolve("$groupId/$artifactId/$version/$fileName").readText()
 
+    var printBuildFileOnFailure: Boolean = false
+
     override fun toString(): String {
         return buildMap {
             put("root files", setup.rootDir.list().orEmpty().toList())
-            setup.rootDir.listFiles().orEmpty().filter { it.isDirectory }.forEach { maybeGroupDir ->
-                maybeGroupDir.listFiles().orEmpty().filter { it.isDirectory }.forEach {
-                    val maybeBuildFile = it.resolve("build.gradle")
-                    if (maybeBuildFile.exists()) {
-                        put(it.name + "/build.gradle", maybeBuildFile.readText())
+            if (printBuildFileOnFailure) {
+                setup.rootDir.listFiles().orEmpty().filter { it.isDirectory }
+                    .forEach { maybeGroupDir ->
+                        maybeGroupDir.listFiles().orEmpty().filter { it.isDirectory }.forEach {
+                            val maybeBuildFile = it.resolve("build.gradle")
+                            if (maybeBuildFile.exists()) {
+                                put(it.name + "/build.gradle", maybeBuildFile.readText())
+                            }
+                        }
                     }
-                }
             }
         }.toString()
     }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 2a58f28d..89a23c4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -51,7 +51,6 @@
 import com.android.build.gradle.TestExtension
 import com.android.build.gradle.TestPlugin
 import com.android.build.gradle.TestedExtension
-import com.android.build.gradle.internal.lint.AndroidLintTask
 import com.android.build.gradle.internal.tasks.AnalyticsRecordingTask
 import com.android.build.gradle.internal.tasks.ListingFileRedirectTask
 import org.gradle.api.GradleException
@@ -383,17 +382,6 @@
             }
         }
 
-        // Remove the lint and column attributes from generated lint baseline XML.
-        project.tasks.withType(AndroidLintTask::class.java).configureEach { task ->
-            if (task.name.startsWith("updateLintBaseline")) {
-                task.doLast {
-                    task.outputs.files.find { it.name == "lint-baseline.xml" }?.let { file ->
-                        file.writeText(removeLineAndColumnAttributes(file.readText()))
-                    }
-                }
-            }
-        }
-
         // Remove the android:targetSdkVersion element from the manifest used for AARs.
         project.extensions.getByType().onVariants { variant ->
             project.tasks.register(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index bc5938e..fb481768 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -95,17 +95,19 @@
         } else { null }
     }
 
+    /**
+     * Configures all ios targets supported by AndroidX.
+     */
     @JvmOverloads
-    fun iosArm32(
+    fun ios(
         block: Action? = null
-    ): KotlinNativeTarget? {
-        return if (project.enableMac()) {
-            kotlinExtension.iosArm32().also {
-                block?.execute(it)
-            }
-        } else { null }
+    ): List {
+        return listOfNotNull(
+            iosX64(block),
+            iosArm64(block),
+            iosSimulatorArm64(block)
+        )
     }
-
     @JvmOverloads
     fun iosX64(
         block: Action? = null
@@ -118,6 +120,17 @@
     }
 
     @JvmOverloads
+    fun iosSimulatorArm64(
+        block: Action? = null
+    ): KotlinNativeTarget? {
+        return if (project.enableMac()) {
+            kotlinExtension.iosSimulatorArm64().also {
+                block?.execute(it)
+            }
+        } else { null }
+    }
+
+    @JvmOverloads
     fun linuxX64(
         block: Action? = null
     ): KotlinNativeTargetWithHostTests? {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index fb9439f..fe5eb3d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -41,7 +41,6 @@
 import org.gradle.api.tasks.bundling.ZipEntryCompression
 import org.gradle.build.event.BuildEventsListenerRegistry
 import org.gradle.kotlin.dsl.extra
-import org.jetbrains.kotlin.gradle.plugin.sources.CleanupStaleSourceSetMetadataEntriesService
 
 abstract class AndroidXRootImplPlugin : Plugin {
     @Suppress("UnstableApiUsage")
@@ -58,7 +57,6 @@
     private fun Project.configureRootProject() {
         project.validateAllAndroidxArgumentsAreRecognized()
         tasks.register("listAndroidXProperties", ListAndroidXPropertiesTask::class.java)
-        this.disableKmpKlibCleanupService()
         setDependencyVersions()
         configureKtlintCheckFile()
         tasks.register(CheckExternalDependencyLicensesTask.TASK_NAME)
@@ -240,35 +238,4 @@
         androidx.build.dependencies.agpVersion = getVersionByName("androidGradlePlugin")
         androidx.build.dependencies.guavaVersion = getVersionByName("guavaJre")
     }
-
-    /**
-     * This function applies the workaround for b/236850628.
-     * There is a BuildService in KMP Gradle Plugin 1.7.0 that tries to cleanup unused klibs,
-     * which is over-eager and causes missing dependencies in the Studio UI. This function simply
-     * breaks the inputs of that cleanup function by intercepting the service creation and changing
-     * its parameters.
-     *
-     * That code is already removed in KMP main branch and we should be able to remove this
-     * workaround after updating to 1.7.20.
-     *
-     * see b/236850628#comment25 for more details.
-     */
-    private fun Project.disableKmpKlibCleanupService() {
-        project.gradle.sharedServices.registrations.whenObjectAdded { serviceRegistration ->
-            if (serviceRegistration.name == "cleanup-stale-sourceset-metadata") {
-                val params = serviceRegistration.parameters as
-                    CleanupStaleSourceSetMetadataEntriesService.Parameters
-                val tmpDirectory = project.layout.buildDirectory.dir("klib-cleanup-workaround")
-                params.projectStorageRoot.set(
-                    tmpDirectory.map { it.asFile }
-                )
-                params.projectStorageDirectories.set(emptyMap())
-                // make sure the store root is not changed afterwards. we don't need to set this
-                // for projectStorageDirectories as the files are deleted from projectStorageRoot,
-                // not projectStorageDirectories.
-                // https://github.com/JetBrains/kotlin/blob/47beef16f38ea315bf4d7d4d3faf34b0b952b613/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetMetadataStorageForIde.kt#L26
-                params.projectStorageRoot.disallowChanges()
-            }
-        }
-    }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 2c76d84..288c32b8 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -19,6 +19,7 @@
 import androidx.build.dependencyTracker.AffectedModuleDetector
 import com.android.build.api.dsl.Lint
 import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
+import com.android.build.gradle.internal.lint.AndroidLintTask
 import java.io.File
 import java.util.Locale
 import org.gradle.api.GradleException
@@ -171,6 +172,17 @@
         )
     }
 
+    tasks.withType(AndroidLintTask::class.java).configureEach { task ->
+        // Remove the lint and column attributes from generated lint baseline XML.
+        if (task.name.startsWith("updateLintBaseline")) {
+            task.doLast {
+                task.outputs.files.find { it.name == "lint-baseline.xml" }?.let { file ->
+                    file.writeText(removeLineAndColumnAttributes(file.readText()))
+                }
+            }
+        }
+    }
+
     // Lint is configured entirely in finalizeDsl so that individual projects cannot easily
     // disable individual checks in the DSL for any reason.
     lint.apply {
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/PublishExtension.kt b/buildSrc/public/src/main/kotlin/androidx/build/PublishExtension.kt
index 5f2c434..0b79b6d 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/PublishExtension.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/PublishExtension.kt
@@ -25,6 +25,7 @@
     var js = Publish.UNSET
     var mac = Publish.UNSET
     var linux = Publish.UNSET
+    var ios = Publish.UNSET
 
     /**
      * List of platforms names which should be published to maven. e.g. ["jvm", "js"]
@@ -44,10 +45,13 @@
             if (linux.shouldPublish()) {
                 platforms.addAll(linuxPlatforms)
             }
+            if (ios.shouldPublish()) {
+                platforms.addAll(iosPlatforms)
+            }
             return platforms
         }
     private val allExtendedPlatforms
-        get() = listOf(jvm, js, mac, linux)
+        get() = listOf(jvm, js, mac, linux, ios)
     private val allPlatforms
         get() = listOf(android) + allExtendedPlatforms
     private val activeExtendedPlatforms
@@ -73,7 +77,11 @@
         private const val MAC_ARM_64 = "macosarm64"
         private const val MAC_OSX_64 = "macosx64"
         private const val LINUX_64 = "linuxx64"
+        private const val IOS_SIMULATOR_ARM_64 = "iossimulatorarm64"
+        private const val IOS_X_64 = "iosx64"
+        private const val IOS_ARM_64 = "iosarm64"
         private val macPlatforms = listOf(MAC_ARM_64, MAC_OSX_64)
         private val linuxPlatforms = listOf(LINUX_64)
+        private val iosPlatforms = listOf(IOS_SIMULATOR_ARM_64, IOS_ARM_64, IOS_X_64)
     }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index a524d23..5f74052 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -45,11 +45,16 @@
  * [android.hardware.camera2.CameraDevice] and [android.hardware.camera2.CameraCaptureSession] via
  * the [CameraGraph] interface.
  */
-public class CameraPipe(config: Config, threadConfig: ThreadConfig = ThreadConfig()) {
+public class CameraPipe(config: Config) {
+
+    @Deprecated("threadConfig should be specified on config.")
+    @Suppress("UNUSED_PARAMETER")
+    public constructor(config: Config, threadConfig: ThreadConfig) : this(config)
+
     private val debugId = cameraPipeIds.incrementAndGet()
     private val component: CameraPipeComponent = DaggerCameraPipeComponent.builder()
         .cameraPipeConfigModule(CameraPipeConfigModule(config))
-        .threadConfigModule(ThreadConfigModule(threadConfig))
+        .threadConfigModule(ThreadConfigModule(config.threadConfig))
         .build()
 
     /**
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
index 4adcc3c..65b43a2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
@@ -431,13 +431,19 @@
                 || AF_CONVERGED_STATE_SET.contains(captureResult.getAfState());
 
         boolean isAeReady;
+        boolean isAeModeOff = totalCaptureResult.get(CaptureResult.CONTROL_AE_MODE)
+                == CaptureResult.CONTROL_AE_MODE_OFF;
         if (isTorchAsFlash) {
-            isAeReady = AE_TORCH_AS_FLASH_CONVERGED_STATE_SET.contains(captureResult.getAeState());
+            isAeReady = isAeModeOff
+                    || AE_TORCH_AS_FLASH_CONVERGED_STATE_SET.contains(captureResult.getAeState());
         } else {
-            isAeReady = AE_CONVERGED_STATE_SET.contains(captureResult.getAeState());
+            isAeReady = isAeModeOff || AE_CONVERGED_STATE_SET.contains(captureResult.getAeState());
         }
 
-        boolean isAwbReady = AWB_CONVERGED_STATE_SET.contains(captureResult.getAwbState());
+        boolean isAwbModeOff = totalCaptureResult.get(CaptureResult.CONTROL_AWB_MODE)
+                == CaptureResult.CONTROL_AWB_MODE_OFF;
+        boolean isAwbReady = isAwbModeOff
+                || AWB_CONVERGED_STATE_SET.contains(captureResult.getAwbState());
 
         Logger.d(TAG, "checkCaptureResult, AE=" + captureResult.getAeState()
                 + " AF =" + captureResult.getAfState()
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
index 5706eea..cdeadb1 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
@@ -67,6 +67,7 @@
 import java.util.concurrent.ScheduledFuture
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
 import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
@@ -100,6 +101,7 @@
         CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO,
         CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED,
         CaptureResult.CONTROL_AWB_MODE to CaptureResult.CONTROL_AWB_MODE_AUTO,
+        CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_ON,
     )
 
     private val resultConverged: Map, Any> = mapOf(
@@ -109,6 +111,15 @@
         CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_CONVERGED,
     )
 
+    private val resultConvergedWith3AModeOff: Map, Any> = 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,
+        CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_INACTIVE,
+        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_INACTIVE,
+        CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_INACTIVE,
+    )
+
     private val fakeStillCaptureSurface = ImmediateSurface(Surface(SurfaceTexture(0)))
 
     private val singleRequest = CaptureConfig.Builder().apply {
@@ -923,6 +934,32 @@
         }
     }
 
+    @Test
+    fun skip3AConvergenceInFlashOn_when3AModeOff(): Unit = runBlocking {
+        // Arrange. Not have the quirk.
+        val cameraControl = createCameraControl(quirks = Quirks(emptyList())).apply {
+            flashMode = FLASH_MODE_ON // Set flash ON
+            simulateRepeatingResult(initialDelay = 100) // Make sures flashMode is updated.
+        }
+
+        // Act.
+        val deferred = cameraControl.submitStillCaptureRequests(
+            listOf(singleRequest),
+            ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
+            ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
+        )
+        // Switch the repeating result to 3A converged state with 3A modes being set to OFF.
+        cameraControl.simulateRepeatingResult(
+            initialDelay = 500,
+            resultParameters = resultConvergedWith3AModeOff
+        )
+
+        // Ensure 3A is converged (skips 3A check) and capture request is sent.
+        withTimeout(2000) {
+            assertThat(deferred.await())
+        }
+    }
+
     private fun Camera2CameraControlImpl.waitForSessionConfig(
         checkResult: (sessionConfig: SessionConfig) -> Boolean = { true }
     ) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
new file mode 100644
index 0000000..b4d7822
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
@@ -0,0 +1,46 @@
+/*
+ * 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.camera.core;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The effect interface which all other effects are built on top of.
+ *
+ * 

A CameraEffect provides functionality to inject post processing to camera output. + * + * @hide + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public interface CameraEffect { + + /** + * Bitmask options for the targets of the effect. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(RestrictTo.Scope.LIBRARY) + @IntDef(flag = true, value = {SurfaceEffect.PREVIEW, SurfaceEffect.VIDEO_CAPTURE, + ImageEffect.IMAGE_CAPTURE}) + @interface Targets { + } +}

diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java
new file mode 100644
index 0000000..9e32538
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java
@@ -0,0 +1,35 @@
+/*
+ * 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.camera.core;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Interface for injecting a {@link ImageProxy}-based post-processing effect into CameraX.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface ImageEffect extends CameraEffect {
+
+    /**
+     * Bitmask option to indicate that CameraX applies this effect to {@link ImageCapture}.
+     */
+    int IMAGE_CAPTURE = 1 << 2;
+
+    // TODO(b/229629890): create the public interface for post-processing images.
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
index 1fa1a7e..2f663178 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
@@ -35,17 +35,15 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface SurfaceEffect {
+public interface SurfaceEffect extends CameraEffect {
 
     /**
-     * Bitmask option to indicate that this Surface will be used by CameraX as the output of the
-     * {@link Preview} {@link UseCase}.
+     * Bitmask option to indicate that CameraX applies this effect to {@link Preview}.
      */
     int PREVIEW = 1;
 
     /**
-     * Bitmask option to indicate that this Surface will be used by CameraX as the output of
-     * video capture {@link UseCase}.
+     * Bitmask option to indicate that CameraX applies this effect to {@code VideoCapture}.
      */
     int VIDEO_CAPTURE = 1 << 1;
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index 8c73719..35753e0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -68,6 +68,7 @@
      * {@link Surface} will be used for sharing a single stream for both preview and video capture.
      * 
      */
+    @CameraEffect.Targets
     int getTargets();
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
index f403a64..c66cd10 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
@@ -127,14 +127,6 @@
         );
     }
 
-    @NonNull
-    @Override
-    public Executor getExecutor() {
-        // TODO(b/237702347): remove all the mGlExecutor.execute() call once this class is only
-        //  accessed on the given thread.
-        return mGlExecutor;
-    }
-
     /**
      * Release the DefaultSurfaceEffect
      */
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
index 7df74b7..0c1564e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
@@ -33,6 +33,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraEffect;
 import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
 import androidx.camera.core.SurfaceRequest;
@@ -74,6 +75,7 @@
     private final Rect mCropRect;
     private final int mRotationDegrees;
     private final boolean mMirroring;
+    @CameraEffect.Targets
     private final int mTargets;
 
     // Guarded by main thread.
@@ -88,7 +90,7 @@
      * Please see the getters to understand the parameters.
      */
     public SettableSurface(
-            int targets,
+            @CameraEffect.Targets int targets,
             @NonNull Size size,
             int format,
             @NonNull Matrix sensorToBufferTransform,
@@ -106,7 +108,7 @@
         mSurfaceFuture = CallbackToFutureAdapter.getFuture(
                 completer -> {
                     mCompleter = completer;
-                    return null;
+                    return "SettableFuture size: " + size + " hashCode: " + hashCode();
                 });
     }
 
@@ -256,6 +258,7 @@
     /**
      * This field indicates that what purpose the {@link Surface} will be used for.
      */
+    @CameraEffect.Targets
     public int getTargets() {
         return mTargets;
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
index 4b0749c..ea3640d1 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
@@ -16,26 +16,17 @@
 
 package androidx.camera.core.processing;
 
-import androidx.annotation.NonNull;
 import androidx.camera.core.SurfaceEffect;
 
-import java.util.concurrent.Executor;
-
 /**
  * An internal {@link SurfaceEffect} that is releasable.
+ *
+ * 

Note: the implementation of this interface must be thread-safe. e.g. methods can be + * safely invoked on any thread. */ public interface SurfaceEffectInternal extends SurfaceEffect { /** - * Gets the executor on which the interface will be invoked. - * - *

For external implementations, the executor is provided when the {@link SurfaceEffect} - * is set. Internal implementations must provide the executor themselves. - */ - @NonNull - Executor getExecutor(); - - /** * Releases all the resources allocated by the effect. * *

An effect created by CameraX should be released by CameraX when it's no longer needed.

diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
index 687128c..cc3473f6 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
@@ -26,7 +26,6 @@
 import androidx.camera.core.impl.utils.Threads;
 
 import java.util.Collections;
-import java.util.concurrent.Executor;
 
 /**
  * A {@link Node} implementation that wraps around the public {@link SurfaceEffect} interface.
@@ -43,8 +42,7 @@
 @SuppressWarnings("UnusedVariable")
 public class SurfaceEffectNode implements Node {
 
-    private final SurfaceEffect mSurfaceEffect;
-    private final Executor mExecutor;
+    private final SurfaceEffectInternal mSurfaceEffect;
     // TODO(b/233680187): keep track of the state of the node so that the pipeline can be
     //  recreated without restarting.
 
@@ -57,11 +55,9 @@
      *  in the output edge and the 4x4 matrix passing to the GL renderer.
      *
      * @param surfaceEffect the interface to wrap around.
-     * @param executor      the executor on which the {@link SurfaceEffect} methods are invoked.
      */
-    public SurfaceEffectNode(@NonNull SurfaceEffect surfaceEffect, @NonNull Executor executor) {
+    public SurfaceEffectNode(@NonNull SurfaceEffectInternal surfaceEffect) {
         mSurfaceEffect = surfaceEffect;
-        mExecutor = executor;
     }
 
     /**
@@ -101,8 +97,6 @@
     @Override
     public void release() {
         // TODO: Call #close() on the output SurfaceOut#getSurface
-        if (mSurfaceEffect instanceof SurfaceEffectInternal) {
-            ((SurfaceEffectInternal) mSurfaceEffect).release();
-        }
+        mSurfaceEffect.release();
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
new file mode 100644
index 0000000..ed3f38b
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.camera.core.processing;
+
+import static androidx.core.util.Preconditions.checkState;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceRequest;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A wrapper of a pair of {@link SurfaceEffect} and {@link Executor}.
+ *
+ * 

Wraps the external {@link SurfaceEffect} and {@link Executor} provided by the app. It + * makes sure that CameraX always invoke the {@link SurfaceEffect} on the correct {@link Executor}. + */ +public class SurfaceEffectWithExecutor implements SurfaceEffectInternal { + + @NonNull + private final SurfaceEffect mSurfaceEffect; + @NonNull + private final Executor mExecutor; + + public SurfaceEffectWithExecutor( + @NonNull SurfaceEffect surfaceEffect, + @NonNull Executor executor) { + checkState(!(surfaceEffect instanceof SurfaceEffectInternal), + "SurfaceEffectInternal should always be thread safe. Do not wrap."); + mSurfaceEffect = surfaceEffect; + mExecutor = executor; + } + + @Override + public void onInputSurface(@NonNull SurfaceRequest request) { + mExecutor.execute(() -> mSurfaceEffect.onInputSurface(request)); + } + + @Override + public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) { + mExecutor.execute(() -> mSurfaceEffect.onOutputSurface(surfaceOutput)); + } + + @Override + public void release() { + // No-op. External SurfaceEffect should not be released by CameraX. + } +}

diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
index 10cd405..707b04d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
@@ -19,9 +19,7 @@
 import android.os.Build
 import androidx.camera.core.SurfaceOutput
 import androidx.camera.core.SurfaceRequest
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
 import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
@@ -40,21 +38,16 @@
     fun releaseNode_effectIsReleased() {
         // Arrange: set up releasable effect and the wrapping node.
         var isReleased = false
-        val releasableEffect = object :
-            SurfaceEffectInternal {
+        val releasableEffect = object : SurfaceEffectInternal {
             override fun onInputSurface(request: SurfaceRequest) {}
 
             override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
 
-            override fun getExecutor(): Executor {
-                return directExecutor()
-            }
-
             override fun release() {
                 isReleased = true
             }
         }
-        val node = SurfaceEffectNode(releasableEffect, directExecutor())
+        val node = SurfaceEffectNode(releasableEffect)
 
         // Act: release the node.
         node.release()
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
new file mode 100644
index 0000000..86b492e
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.camera.core.processing
+
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper.getMainLooper
+import android.util.Size
+import androidx.camera.core.SurfaceEffect
+import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+import androidx.camera.testing.fakes.FakeCamera
+import com.google.common.truth.Truth.assertThat
+import java.lang.Thread.currentThread
+import java.util.concurrent.Executor
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [SurfaceEffectWithExecutor].
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class SurfaceEffectWithExecutorTest {
+
+    companion object {
+        private val SIZE = Size(640, 480)
+    }
+
+    lateinit var executorThread: HandlerThread
+    lateinit var executor: Executor
+
+    @Before
+    fun setup() {
+        executorThread = HandlerThread("")
+        executorThread.start()
+        executor = CameraXExecutors.newHandlerExecutor(Handler(executorThread.looper))
+    }
+
+    @After
+    fun tearDown() {
+        executorThread.quitSafely()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun initWithSurfaceEffectInternal_throwsException() {
+        SurfaceEffectWithExecutor(object : SurfaceEffectInternal {
+            override fun onInputSurface(request: SurfaceRequest) {}
+
+            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
+
+            override fun release() {}
+        }, mainThreadExecutor())
+    }
+
+    @Test
+    fun invokeEffect_invokedOnEffectExecutor() {
+        // Arrange: track which thread the methods are invoked on.
+        var onInputSurfaceInvokedThread: Thread? = null
+        var onOutputSurfaceInvokedThread: Thread? = null
+        val effectWithExecutor = SurfaceEffectWithExecutor(object : SurfaceEffect {
+            override fun onInputSurface(request: SurfaceRequest) {
+                onInputSurfaceInvokedThread = currentThread()
+            }
+
+            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
+                onOutputSurfaceInvokedThread = currentThread()
+            }
+        }, executor)
+        // Act: invoke methods.
+        effectWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false))
+        effectWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
+        shadowOf(getMainLooper()).idle()
+        shadowOf(executorThread.looper).idle()
+        // Assert: it's the executor thread.
+        assertThat(onInputSurfaceInvokedThread).isEqualTo(executorThread)
+        assertThat(onOutputSurfaceInvokedThread).isEqualTo(executorThread)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index 94e5745..f8a2ad5 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -20,7 +20,6 @@
   }
 
   @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
-    method public long getFileSizeLimit();
     method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
   }
 
@@ -28,24 +27,24 @@
     ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
     method public androidx.camera.video.FileDescriptorOutputOptions build();
     method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
     method public java.io.File getFile();
-    method public long getFileSizeLimit();
   }
 
   @RequiresApi(21) public static final class FileOutputOptions.Builder {
     ctor public FileOutputOptions.Builder(java.io.File);
     method public androidx.camera.video.FileOutputOptions build();
     method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
     method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
-    method public long getFileSizeLimit();
     field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
   }
 
@@ -54,10 +53,12 @@
     method public androidx.camera.video.MediaStoreOutputOptions build();
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public abstract class OutputOptions {
-    method public abstract long getFileSizeLimit();
+    method public long getFileSizeLimit();
+    method public android.location.Location? getLocation();
     field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
   }
 
diff --git a/camera/camera-video/api/public_plus_experimental_current.txt b/camera/camera-video/api/public_plus_experimental_current.txt
index 94e5745..f8a2ad5 100644
--- a/camera/camera-video/api/public_plus_experimental_current.txt
+++ b/camera/camera-video/api/public_plus_experimental_current.txt
@@ -20,7 +20,6 @@
   }
 
   @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
-    method public long getFileSizeLimit();
     method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
   }
 
@@ -28,24 +27,24 @@
     ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
     method public androidx.camera.video.FileDescriptorOutputOptions build();
     method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
     method public java.io.File getFile();
-    method public long getFileSizeLimit();
   }
 
   @RequiresApi(21) public static final class FileOutputOptions.Builder {
     ctor public FileOutputOptions.Builder(java.io.File);
     method public androidx.camera.video.FileOutputOptions build();
     method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
     method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
-    method public long getFileSizeLimit();
     field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
   }
 
@@ -54,10 +53,12 @@
     method public androidx.camera.video.MediaStoreOutputOptions build();
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public abstract class OutputOptions {
-    method public abstract long getFileSizeLimit();
+    method public long getFileSizeLimit();
+    method public android.location.Location? getLocation();
     field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
   }
 
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index 94e5745..f8a2ad5 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -20,7 +20,6 @@
   }
 
   @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
-    method public long getFileSizeLimit();
     method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
   }
 
@@ -28,24 +27,24 @@
     ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
     method public androidx.camera.video.FileDescriptorOutputOptions build();
     method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
     method public java.io.File getFile();
-    method public long getFileSizeLimit();
   }
 
   @RequiresApi(21) public static final class FileOutputOptions.Builder {
     ctor public FileOutputOptions.Builder(java.io.File);
     method public androidx.camera.video.FileOutputOptions build();
     method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
     method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
-    method public long getFileSizeLimit();
     field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
   }
 
@@ -54,10 +53,12 @@
     method public androidx.camera.video.MediaStoreOutputOptions build();
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
     method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
   }
 
   @RequiresApi(21) public abstract class OutputOptions {
-    method public abstract long getFileSizeLimit();
+    method public long getFileSizeLimit();
+    method public android.location.Location? getLocation();
     field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
   }
 
diff --git a/camera/camera-video/build.gradle b/camera/camera-video/build.gradle
index b4e94bf..8a3a750 100644
--- a/camera/camera-video/build.gradle
+++ b/camera/camera-video/build.gradle
@@ -58,6 +58,7 @@
     androidTestImplementation(libs.truth)
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it's own MockMaker
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy) // DexMaker has it's own MockMaker
+    androidTestImplementation(libs.autoValueAnnotations)
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing"))
     androidTestImplementation(libs.kotlinStdlib)
@@ -67,6 +68,7 @@
     androidTestImplementation libs.mockitoKotlin, {
         exclude group: 'org.mockito' // to keep control on the mockito version
     }
+    androidTestAnnotationProcessor(libs.autoValue)
 }
 
 android {
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/FakeOutputOptions.java b/camera/camera-video/src/androidTest/java/androidx/camera/video/FakeOutputOptions.java
new file mode 100644
index 0000000..06cf635
--- /dev/null
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/FakeOutputOptions.java
@@ -0,0 +1,63 @@
+/*
+ * 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.camera.video;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.google.auto.value.AutoValue;
+
+/** A fake implementation of {@link OutputOptions}. */
+// Java is used because @AutoValue is required.
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class FakeOutputOptions extends OutputOptions {
+
+    private FakeOutputOptions(@NonNull FakeOutputOptionsInternal fakeOutputOptionsInternal) {
+        super(fakeOutputOptionsInternal);
+    }
+
+    /** The builder of the {@link FakeOutputOptions} object. */
+    @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+    public static final class Builder extends OutputOptions.Builder {
+
+        /** Creates a builder of the {@link FakeOutputOptions}. */
+        public Builder() {
+            super(new AutoValue_FakeOutputOptions_FakeOutputOptionsInternal.Builder());
+        }
+
+        /** Builds the {@link FakeOutputOptions} instance. */
+        @Override
+        @NonNull
+        public FakeOutputOptions build() {
+            return new FakeOutputOptions(
+                    ((FakeOutputOptionsInternal.Builder) mRootInternalBuilder).build());
+        }
+    }
+
+    @AutoValue
+    abstract static class FakeOutputOptionsInternal extends OutputOptions.OutputOptionsInternal {
+
+        @AutoValue.Builder
+        abstract static class Builder extends OutputOptions.OutputOptionsInternal.Builder {
+
+            @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class
+            @Override
+            @NonNull
+            abstract FakeOutputOptionsInternal build();
+        }
+    }
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
index c221b5b..9c73beb 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
@@ -19,13 +19,16 @@
 import android.content.ContentResolver
 import android.content.ContentValues
 import android.content.Context
+import android.location.Location
 import android.os.ParcelFileDescriptor
 import android.provider.MediaStore
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.io.File
@@ -136,4 +139,52 @@
         }
         savedFile.delete()
     }
+
+    @Test
+    fun defaultLocationIsNull() {
+        val outputOptions = FakeOutputOptions.Builder().build()
+
+        assertThat(outputOptions.location).isNull()
+    }
+
+    @Test
+    fun setValidLocation() {
+        listOf(
+            createLocation(0.0, 0.0),
+            createLocation(90.0, 180.0),
+            createLocation(-90.0, -180.0),
+            createLocation(10.1234, -100.5678),
+        ).forEach { location ->
+            val outputOptions = FakeOutputOptions.Builder().setLocation(location).build()
+
+            assertWithMessage("Test $location failed")
+                .that(outputOptions.location).isEqualTo(location)
+        }
+    }
+
+    @Test
+    fun setInvalidLocation() {
+        listOf(
+            createLocation(Double.NaN, 0.0),
+            createLocation(0.0, Double.NaN),
+            createLocation(90.5, 0.0),
+            createLocation(-90.5, 0.0),
+            createLocation(0.0, 180.5),
+            createLocation(0.0, -180.5),
+        ).forEach { location ->
+            assertThrows(IllegalArgumentException::class.java) {
+                FakeOutputOptions.Builder().setLocation(location)
+            }
+        }
+    }
+
+    private fun createLocation(
+        latitude: Double,
+        longitude: Double,
+        provider: String = "FakeProvider"
+    ): Location =
+        Location(provider).apply {
+            this.latitude = latitude
+            this.longitude = longitude
+        }
 }
\ No newline at end of file
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index a1ffe7e..3d62333 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -26,6 +26,7 @@
 import android.content.ContentValues
 import android.content.Context
 import android.graphics.SurfaceTexture
+import android.location.Location
 import android.media.MediaMetadataRetriever
 import android.media.MediaRecorder
 import android.net.Uri
@@ -66,6 +67,7 @@
 import androidx.testutils.assertThrows
 import androidx.testutils.fail
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import java.io.File
 import java.util.concurrent.Executor
 import java.util.concurrent.Semaphore
@@ -575,6 +577,14 @@
     }
 
     @Test
+    fun setLocation() {
+        // TODO(leohuang): add a test to verify negative latitude and longitude.
+        //  MediaMuxer.setLocation() causes a little loss of precision on negative values.
+        //  See b/232327925.
+        runLocationTest(createLocation(25.033267462243586, 121.56454121737946))
+    }
+
+    @Test
     fun checkStreamState() {
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
@@ -1176,34 +1186,69 @@
     }
 
     private fun checkFileAudio(uri: Uri, hasAudio: Boolean) {
-        val mediaRetriever = MediaMetadataRetriever()
-        mediaRetriever.apply {
-            setDataSource(context, uri)
-            val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO)
+        MediaMetadataRetriever().apply {
+            try {
+                setDataSource(context, uri)
+                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO)
 
-            assertThat(value).isEqualTo(
-                if (hasAudio) {
-                    "yes"
-                } else {
-                    null
-                }
-            )
+                assertThat(value).isEqualTo(
+                    if (hasAudio) {
+                        "yes"
+                    } else {
+                        null
+                    }
+                )
+            } finally {
+                release()
+            }
         }
     }
 
     private fun checkFileVideo(uri: Uri, hasVideo: Boolean) {
-        val mediaRetriever = MediaMetadataRetriever()
-        mediaRetriever.apply {
-            setDataSource(context, uri)
-            val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)
+        MediaMetadataRetriever().apply {
+            try {
+                setDataSource(context, uri)
+                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)
 
-            assertThat(value).isEqualTo(
-                if (hasVideo) {
-                    "yes"
-                } else {
-                    null
-                }
-            )
+                assertThat(value).isEqualTo(
+                    if (hasVideo) {
+                        "yes"
+                    } else {
+                        null
+                    }
+                )
+            } finally {
+                release()
+            }
+        }
+    }
+
+    private fun checkLocation(uri: Uri, location: Location) {
+        MediaMetadataRetriever().apply {
+            try {
+                setDataSource(context, uri)
+                // Only test on mp4 output format, others will be ignored.
+                val mime = extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
+                assumeTrue("Unsupported mime = $mime",
+                    "video/mp4".equals(mime, ignoreCase = true))
+                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
+                assertThat(value).isNotNull()
+                // ex: (90, 180) => "+90.0000+180.0000/" (ISO-6709 standard)
+                val matchGroup =
+                    "([\\+-]?[0-9]+(\\.[0-9]+)?)([\\+-]?[0-9]+(\\.[0-9]+)?)".toRegex()
+                        .find(value!!) ?: fail("Fail on checking location metadata: $value")
+                val lat = matchGroup.groupValues[1].toDouble()
+                val lon = matchGroup.groupValues[3].toDouble()
+
+                // MediaMuxer.setLocation rounds the value to 4 decimal places
+                val tolerance = 0.0001
+                assertWithMessage("Fail on latitude. $lat($value) vs ${location.latitude}")
+                    .that(lat).isWithin(tolerance).of(location.latitude)
+                assertWithMessage("Fail on longitude. $lon($value) vs ${location.longitude}")
+                    .that(lon).isWithin(tolerance).of(location.longitude)
+            } finally {
+                release()
+            }
         }
     }
 
@@ -1256,4 +1301,43 @@
         recording.close()
         file.delete()
     }
+
+    private fun runLocationTest(location: Location) {
+        val recorder = Recorder.Builder().build()
+        invokeSurfaceRequest(recorder)
+        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        val outputOptions = FileOutputOptions.Builder(file)
+            .setLocation(location)
+            .build()
+
+        val recording = recorder
+            .prepareRecording(context, outputOptions)
+            .start(CameraXExecutors.directExecutor(), videoRecordEventListener)
+
+        val inOrder = inOrder(videoRecordEventListener)
+        inOrder.verify(videoRecordEventListener, timeout(5000L))
+            .accept(any(VideoRecordEvent.Start::class.java))
+        inOrder.verify(videoRecordEventListener, timeout(15000L).atLeast(5))
+            .accept(any(VideoRecordEvent.Status::class.java))
+
+        recording.stopSafely()
+
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
+            .accept(any(VideoRecordEvent.Finalize::class.java))
+
+        val uri = Uri.fromFile(file)
+        checkLocation(uri, location)
+
+        file.delete()
+    }
+
+    private fun createLocation(
+        latitude: Double,
+        longitude: Double,
+        provider: String = "FakeProvider"
+    ): Location =
+        Location(provider).apply {
+            this.latitude = latitude
+            this.longitude = longitude
+        }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
index 7c69be4..495ca67 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
@@ -43,8 +43,7 @@
 
     FileDescriptorOutputOptions(
             @NonNull FileDescriptorOutputOptionsInternal fileDescriptorOutputOptionsInternal) {
-        Preconditions.checkNotNull(fileDescriptorOutputOptionsInternal,
-                "FileDescriptorOutputOptionsInternal can't be null.");
+        super(fileDescriptorOutputOptionsInternal);
         mFileDescriptorOutputOptionsInternal = fileDescriptorOutputOptionsInternal;
     }
 
@@ -58,14 +57,6 @@
         return mFileDescriptorOutputOptionsInternal.getParcelFileDescriptor();
     }
 
-    /**
-     * Gets the limit for the file length in bytes.
-     */
-    @Override
-    public long getFileSizeLimit() {
-        return mFileDescriptorOutputOptionsInternal.getFileSizeLimit();
-    }
-
     @Override
     @NonNull
     public String toString() {
@@ -93,12 +84,10 @@
 
     /** The builder of the {@link FileDescriptorOutputOptions} object. */
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-    public static final class Builder implements
+    public static final class Builder extends
             OutputOptions.Builder {
-        private final FileDescriptorOutputOptionsInternal.Builder mInternalBuilder =
-                new AutoValue_FileDescriptorOutputOptions_FileDescriptorOutputOptionsInternal
-                        .Builder()
-                        .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+
+        private final FileDescriptorOutputOptionsInternal.Builder mInternalBuilder;
 
         /**
          * Creates a builder of the {@link FileDescriptorOutputOptions} with a file descriptor.
@@ -106,7 +95,10 @@
          * @param fileDescriptor the file descriptor to use as the output destination.
          */
         public Builder(@NonNull ParcelFileDescriptor fileDescriptor) {
+            super(new AutoValue_FileDescriptorOutputOptions_FileDescriptorOutputOptionsInternal
+                    .Builder());
             Preconditions.checkNotNull(fileDescriptor, "File descriptor can't be null.");
+            mInternalBuilder = (FileDescriptorOutputOptionsInternal.Builder) mRootInternalBuilder;
             mInternalBuilder.setParcelFileDescriptor(fileDescriptor);
         }
 
@@ -124,8 +116,7 @@
         @Override
         @NonNull
         public Builder setFileSizeLimit(long fileSizeLimitBytes) {
-            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
-            return this;
+            return super.setFileSizeLimit(fileSizeLimitBytes);
         }
 
         /** Builds the {@link FileDescriptorOutputOptions} instance. */
@@ -137,18 +128,17 @@
     }
 
     @AutoValue
-    abstract static class FileDescriptorOutputOptionsInternal {
+    abstract static class FileDescriptorOutputOptionsInternal extends OutputOptionsInternal {
         @NonNull
         abstract ParcelFileDescriptor getParcelFileDescriptor();
-        abstract long getFileSizeLimit();
 
+        @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class
         @AutoValue.Builder
-        abstract static class Builder {
+        abstract static class Builder extends OutputOptionsInternal.Builder {
             @NonNull
             abstract Builder setParcelFileDescriptor(
                     @NonNull ParcelFileDescriptor parcelFileDescriptor);
-            @NonNull
-            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
+            @Override
             @NonNull
             abstract FileDescriptorOutputOptionsInternal build();
         }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
index 131a458..9bff991 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
@@ -39,8 +39,7 @@
     private final FileOutputOptionsInternal mFileOutputOptionsInternal;
 
     FileOutputOptions(@NonNull FileOutputOptionsInternal fileOutputOptionsInternal) {
-        Preconditions.checkNotNull(fileOutputOptionsInternal,
-                "FileOutputOptionsInternal can't be null.");
+        super(fileOutputOptionsInternal);
         mFileOutputOptionsInternal = fileOutputOptionsInternal;
     }
 
@@ -50,14 +49,6 @@
         return mFileOutputOptionsInternal.getFile();
     }
 
-    /**
-     * Gets the limit for the file length in bytes.
-     */
-    @Override
-    public long getFileSizeLimit() {
-        return mFileOutputOptionsInternal.getFileSizeLimit();
-    }
-
     @Override
     @NonNull
     public String toString() {
@@ -85,10 +76,9 @@
 
     /** The builder of the {@link FileOutputOptions} object. */
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-    public static final class Builder implements OutputOptions.Builder {
-        private final FileOutputOptionsInternal.Builder mInternalBuilder =
-                new AutoValue_FileOutputOptions_FileOutputOptionsInternal.Builder()
-                        .setFileSizeLimit(OutputOptions.FILE_SIZE_UNLIMITED);
+    public static final class Builder extends OutputOptions.Builder {
+
+        private final FileOutputOptionsInternal.Builder mInternalBuilder;
 
         /**
          * Creates a builder of the {@link FileOutputOptions} with a file object.
@@ -101,7 +91,9 @@
          */
         @SuppressWarnings("StreamFiles") // FileDescriptor API is in FileDescriptorOutputOptions
         public Builder(@NonNull File file) {
+            super(new AutoValue_FileOutputOptions_FileOutputOptionsInternal.Builder());
             Preconditions.checkNotNull(file, "File can't be null.");
+            mInternalBuilder = (FileOutputOptionsInternal.Builder) mRootInternalBuilder;
             mInternalBuilder.setFile(file);
         }
 
@@ -119,8 +111,7 @@
         @Override
         @NonNull
         public Builder setFileSizeLimit(long fileSizeLimitBytes) {
-            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
-            return this;
+            return super.setFileSizeLimit(fileSizeLimitBytes);
         }
 
         /** Builds the {@link FileOutputOptions} instance. */
@@ -132,20 +123,19 @@
     }
 
     @AutoValue
-    abstract static class FileOutputOptionsInternal {
+    abstract static class FileOutputOptionsInternal extends OutputOptions.OutputOptionsInternal {
+
         @NonNull
         abstract File getFile();
 
-        abstract long getFileSizeLimit();
-
+        @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class
         @AutoValue.Builder
-        abstract static class Builder {
+        abstract static class Builder extends OutputOptions.OutputOptionsInternal.Builder {
+
             @NonNull
             abstract Builder setFile(@NonNull File file);
 
-            @NonNull
-            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
-
+            @Override
             @NonNull
             abstract FileOutputOptionsInternal build();
         }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
index 63d49ee..ebe74cd 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
@@ -70,8 +70,7 @@
 
     MediaStoreOutputOptions(
             @NonNull MediaStoreOutputOptionsInternal mediaStoreOutputOptionsInternal) {
-        Preconditions.checkNotNull(mediaStoreOutputOptionsInternal,
-                "MediaStoreOutputOptionsInternal can't be null.");
+        super(mediaStoreOutputOptionsInternal);
         mMediaStoreOutputOptionsInternal = mediaStoreOutputOptionsInternal;
     }
 
@@ -105,16 +104,6 @@
         return mMediaStoreOutputOptionsInternal.getContentValues();
     }
 
-    /**
-     * Gets the limit for the file length in bytes.
-     *
-     * @see Builder#setFileSizeLimit(long)
-     */
-    @Override
-    public long getFileSizeLimit() {
-        return mMediaStoreOutputOptionsInternal.getFileSizeLimit();
-    }
-
     @Override
     @NonNull
     public String toString() {
@@ -141,12 +130,10 @@
     }
 
     /** The builder of the {@link MediaStoreOutputOptions} object. */
-    public static final class Builder implements
+    public static final class Builder extends
             OutputOptions.Builder {
-        private final MediaStoreOutputOptionsInternal.Builder mInternalBuilder =
-                new AutoValue_MediaStoreOutputOptions_MediaStoreOutputOptionsInternal.Builder()
-                        .setContentValues(EMPTY_CONTENT_VALUES)
-                        .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+
+        private final MediaStoreOutputOptionsInternal.Builder mInternalBuilder;
 
         /**
          * Creates a builder of the {@link MediaStoreOutputOptions} with media store options.
@@ -171,9 +158,13 @@
          * @param collectionUri the URI of the collection to insert into.
          */
         public Builder(@NonNull ContentResolver contentResolver, @NonNull Uri collectionUri) {
+            super(new AutoValue_MediaStoreOutputOptions_MediaStoreOutputOptionsInternal.Builder());
             Preconditions.checkNotNull(contentResolver, "Content resolver can't be null.");
             Preconditions.checkNotNull(collectionUri, "Collection Uri can't be null.");
-            mInternalBuilder.setContentResolver(contentResolver).setCollectionUri(collectionUri);
+            mInternalBuilder = (MediaStoreOutputOptionsInternal.Builder) mRootInternalBuilder;
+            mInternalBuilder.setContentResolver(contentResolver)
+                    .setCollectionUri(collectionUri)
+                    .setContentValues(new ContentValues());
         }
 
         /**
@@ -211,8 +202,7 @@
         @Override
         @NonNull
         public Builder setFileSizeLimit(long fileSizeLimitBytes) {
-            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
-            return this;
+            return super.setFileSizeLimit(fileSizeLimitBytes);
         }
 
         /** Builds the {@link MediaStoreOutputOptions} instance. */
@@ -224,25 +214,24 @@
     }
 
     @AutoValue
-    abstract static class MediaStoreOutputOptionsInternal {
+    abstract static class MediaStoreOutputOptionsInternal extends OutputOptionsInternal {
         @NonNull
         abstract ContentResolver getContentResolver();
         @NonNull
         abstract Uri getCollectionUri();
         @NonNull
         abstract ContentValues getContentValues();
-        abstract long getFileSizeLimit();
 
+        @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class
         @AutoValue.Builder
-        abstract static class Builder {
+        abstract static class Builder extends OutputOptionsInternal.Builder {
             @NonNull
             abstract Builder setContentResolver(@NonNull ContentResolver contentResolver);
             @NonNull
             abstract Builder setCollectionUri(@NonNull Uri collectionUri);
             @NonNull
             abstract Builder setContentValues(@NonNull ContentValues contentValues);
-            @NonNull
-            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
+            @Override
             @NonNull
             abstract MediaStoreOutputOptionsInternal build();
         }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/OutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/OutputOptions.java
index c5c113d..3b6f924 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/OutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/OutputOptions.java
@@ -16,8 +16,12 @@
 
 package androidx.camera.video;
 
+import android.location.Location;
+
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.core.util.Preconditions;
 
 /**
  * Options for configuring output destination for generating a recording.
@@ -36,7 +40,10 @@
     /** Represents an unbound file size. */
     public static final int FILE_SIZE_UNLIMITED = 0;
 
-    OutputOptions() {
+    private final OutputOptionsInternal mOutputOptionsInternal;
+
+    OutputOptions(@NonNull OutputOptionsInternal outputOptionsInternal) {
+        mOutputOptionsInternal = outputOptionsInternal;
     }
 
     /**
@@ -44,12 +51,34 @@
      *
      * @return the file size limit in bytes.
      */
-    public abstract long getFileSizeLimit();
+    public long getFileSizeLimit() {
+        return mOutputOptionsInternal.getFileSizeLimit();
+    }
+
+    /**
+     * Returns a {@link Location} object representing the geographic location where the video was
+     * recorded.
+     *
+     * @return The location object or {@code null} if no location was set.
+     */
+    @Nullable
+    public Location getLocation() {
+        return mOutputOptionsInternal.getLocation();
+    }
 
     /**
      * The builder of the {@link OutputOptions}.
      */
-    interface Builder {
+    @SuppressWarnings("unchecked") // Cast to type B
+    abstract static class Builder {
+
+        final OutputOptionsInternal.Builder mRootInternalBuilder;
+
+        Builder(@NonNull OutputOptionsInternal.Builder builder) {
+            mRootInternalBuilder = builder;
+            // Apply default value
+            mRootInternalBuilder.setFileSizeLimit(FILE_SIZE_UNLIMITED);
+        }
 
         /**
          * Sets the limit for the file length in bytes.
@@ -57,12 +86,66 @@
          * 

If not set, defaults to {@link #FILE_SIZE_UNLIMITED}. */ @NonNull - B setFileSizeLimit(long bytes); + public B setFileSizeLimit(long bytes) { + mRootInternalBuilder.setFileSizeLimit(bytes); + return (B) this; + } + + /** + * Sets a {@link Location} object representing a geographic location where the video was + * recorded. + * + *

When use with {@link Recorder}, the geographic location is stored in udta box if the + * output format is MP4, and is ignored for other formats. The geographic location is + * stored according to ISO-6709 standard. + * + *

If {@code null}, no location information will be saved with the video. Default + * value is {@code null}. + * + * @throws IllegalArgumentException if the latitude of the location is not in the range + * [-90, 90] or the longitude of the location is not in the range [-180, 180]. + */ + @NonNull + public B setLocation(@Nullable Location location) { + if (location != null) { + Preconditions.checkArgument( + location.getLatitude() >= -90 && location.getLatitude() <= 90, + "Latitude must be in the range [-90, 90]"); + Preconditions.checkArgument( + location.getLongitude() >= -180 && location.getLongitude() <= 180, + "Longitude must be in the range [-180, 180]"); + } + mRootInternalBuilder.setLocation(location); + return (B) this; + } /** * Builds the {@link OutputOptions} instance. */ @NonNull - T build(); + abstract T build(); + } + + // A base class of a @AutoValue class + abstract static class OutputOptionsInternal { + + abstract long getFileSizeLimit(); + + @Nullable + abstract Location getLocation(); + + // A base class of a @AutoValue.Builder class + @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class + abstract static class Builder { + + @NonNull + abstract B setFileSizeLimit(long fileSizeLimitBytes); + + @NonNull + abstract B setLocation(@Nullable Location location); + + @NonNull + abstract OutputOptionsInternal build(); + } } }

diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index 66cd575..5cb6438 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -31,6 +31,7 @@
 import android.annotation.SuppressLint;
 import android.content.ContentValues;
 import android.content.Context;
+import android.location.Location;
 import android.media.MediaMuxer;
 import android.media.MediaRecorder;
 import android.media.MediaScannerConnection;
@@ -1512,6 +1513,7 @@
                 return;
             }
 
+            MediaMuxer mediaMuxer;
             try {
                 MediaSpec mediaSpec = getObservableData(mMediaSpec);
                 int muxerOutputFormat =
@@ -1520,7 +1522,7 @@
                                 MediaSpec.outputFormatToMuxerFormat(
                                         MEDIA_SPEC_DEFAULT.getOutputFormat()))
                                 : MediaSpec.outputFormatToMuxerFormat(mediaSpec.getOutputFormat());
-                mMediaMuxer = recordingToStart.performOneTimeMediaMuxerCreation(muxerOutputFormat,
+                mediaMuxer = recordingToStart.performOneTimeMediaMuxerCreation(muxerOutputFormat,
                         uri -> mOutputUri = uri);
             } catch (IOException e) {
                 onInProgressRecordingInternalError(recordingToStart, ERROR_INVALID_OUTPUT_OPTIONS,
@@ -1528,16 +1530,30 @@
                 return;
             }
 
-            // TODO: Add more metadata to MediaMuxer, e.g. location information.
             if (mSurfaceTransformationInfo != null) {
-                mMediaMuxer.setOrientationHint(mSurfaceTransformationInfo.getRotationDegrees());
+                mediaMuxer.setOrientationHint(mSurfaceTransformationInfo.getRotationDegrees());
+            }
+            Location location = recordingToStart.getOutputOptions().getLocation();
+            if (location != null) {
+                try {
+                    mediaMuxer.setLocation((float) location.getLatitude(),
+                            (float) location.getLongitude());
+                } catch (IllegalArgumentException e) {
+                    mediaMuxer.release();
+                    onInProgressRecordingInternalError(recordingToStart,
+                            ERROR_INVALID_OUTPUT_OPTIONS, e);
+                    return;
+                }
             }
 
-            mVideoTrackIndex = mMediaMuxer.addTrack(mVideoOutputConfig.getMediaFormat());
+            mVideoTrackIndex = mediaMuxer.addTrack(mVideoOutputConfig.getMediaFormat());
             if (isAudioEnabled()) {
-                mAudioTrackIndex = mMediaMuxer.addTrack(mAudioOutputConfig.getMediaFormat());
+                mAudioTrackIndex = mediaMuxer.addTrack(mAudioOutputConfig.getMediaFormat());
             }
-            mMediaMuxer.start();
+            mediaMuxer.start();
+
+            // MediaMuxer is successfully initialized, transfer the ownership to Recorder.
+            mMediaMuxer = mediaMuxer;
 
             // Write first data to ensure tracks are not empty
             writeVideoData(videoDataToWrite, recordingToStart);
diff --git a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorViewModel.kt b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorViewModel.kt
index b94fd37..2997a17 100644
--- a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorViewModel.kt
+++ b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.Context.AUDIO_SERVICE
 import android.media.AudioManager
+import androidx.camera.core.Logger
 import androidx.camera.integration.avsync.model.AudioGenerator
 import androidx.camera.integration.avsync.model.CameraHelper
 import androidx.compose.runtime.getValue
@@ -41,6 +42,7 @@
 private const val ACTIVE_INTERVAL_SEC: Double = 1.0
 private const val ACTIVE_DELAY_SEC: Double = 0.0
 private const val VOLUME_PERCENTAGE: Double = 0.6
+private const val TAG = "SignalGeneratorViewModel"
 
 enum class ActivationSignal {
     Active, Inactive
@@ -93,6 +95,7 @@
     }
 
     fun startSignalGeneration() {
+        Logger.d(TAG, "Start signal generation.")
         Preconditions.checkState(isGeneratorReady)
 
         setVolume()
@@ -118,6 +121,7 @@
     }
 
     fun stopSignalGeneration() {
+        Logger.d(TAG, "Stop signal generation.")
         Preconditions.checkState(isGeneratorReady)
 
         isSignalGenerating = false
@@ -126,6 +130,7 @@
     }
 
     fun startRecording(context: Context) {
+        Logger.d(TAG, "Start recording.")
         Preconditions.checkState(isRecorderReady)
 
         cameraHelper.startRecording(context)
@@ -133,6 +138,7 @@
     }
 
     fun stopRecording() {
+        Logger.d(TAG, "Stop recording.")
         Preconditions.checkState(isRecorderReady)
 
         cameraHelper.stopRecording()
diff --git a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/model/CameraHelper.kt b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/model/CameraHelper.kt
index b066ef4..daca14e 100644
--- a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/model/CameraHelper.kt
+++ b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/model/CameraHelper.kt
@@ -20,20 +20,26 @@
 import android.content.ContentResolver
 import android.content.ContentValues
 import android.content.Context
+import android.os.Environment
 import android.provider.MediaStore
 import androidx.annotation.MainThread
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.Logger
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.video.FileOutputOptions
 import androidx.camera.video.MediaStoreOutputOptions
+import androidx.camera.video.PendingRecording
 import androidx.camera.video.Recorder
 import androidx.camera.video.Recording
 import androidx.camera.video.VideoCapture
 import androidx.camera.video.VideoRecordEvent
+import androidx.camera.video.internal.compat.quirk.DeviceQuirks
+import androidx.camera.video.internal.compat.quirk.MediaStoreVideoCannotWrite
 import androidx.concurrent.futures.await
 import androidx.core.content.ContextCompat
 import androidx.core.util.Consumer
 import androidx.lifecycle.LifecycleOwner
+import java.io.File
 
 private const val TAG = "CameraHelper"
 
@@ -67,24 +73,48 @@
     @SuppressLint("MissingPermission")
     fun startRecording(context: Context, eventListener: Consumer? = null) {
         activeRecording = videoCapture!!.let {
-            val pendingRecording = it.output.prepareRecording(
-                context,
-                generateVideoMediaStoreOptions(context.contentResolver)
-            )
-
             val listener = eventListener ?: generateVideoRecordEventListener()
-            pendingRecording.withAudioEnabled().start(
+            prepareRecording(context, it.output).withAudioEnabled().start(
                 ContextCompat.getMainExecutor(context),
                 listener
             )
         }
     }
 
+    private fun prepareRecording(context: Context, recorder: Recorder): PendingRecording {
+        return if (canDeviceWriteToMediaStore()) {
+            recorder.prepareRecording(
+                context,
+                generateVideoMediaStoreOptions(context.contentResolver)
+            )
+        } else {
+            recorder.prepareRecording(
+                context,
+                generateVideoFileOutputOptions()
+            )
+        }
+    }
+
+    private fun canDeviceWriteToMediaStore(): Boolean {
+        return DeviceQuirks.get(MediaStoreVideoCannotWrite::class.java) == null
+    }
+
     fun stopRecording() {
         activeRecording!!.stop()
         activeRecording = null
     }
 
+    private fun generateVideoFileOutputOptions(): FileOutputOptions {
+        val videoFileName = "${generateVideoFileName()}.mp4"
+        val videoFolder = Environment.getExternalStoragePublicDirectory(
+            Environment.DIRECTORY_MOVIES
+        )
+        if (!videoFolder.exists() && !videoFolder.mkdirs()) {
+            Logger.e(TAG, "Failed to create directory: $videoFolder")
+        }
+        return FileOutputOptions.Builder(File(videoFolder, videoFileName)).build()
+    }
+
     private fun generateVideoMediaStoreOptions(
         contentResolver: ContentResolver
     ): MediaStoreOutputOptions {
diff --git a/car/app/app-automotive/api/current.txt b/car/app/app-automotive/api/current.txt
index fdfc2bc..e0b9e41 100644
--- a/car/app/app-automotive/api/current.txt
+++ b/car/app/app-automotive/api/current.txt
@@ -3,7 +3,7 @@
 
   public abstract class BaseCarAppActivity extends androidx.fragment.app.FragmentActivity implements androidx.lifecycle.LifecycleOwner {
     ctor public BaseCarAppActivity();
-    method public void bindToViewModel();
+    method public void bindToViewModel(androidx.car.app.SessionInfo);
     method public android.content.ComponentName? retrieveServiceComponentName();
   }
 
diff --git a/car/app/app-automotive/api/public_plus_experimental_current.txt b/car/app/app-automotive/api/public_plus_experimental_current.txt
index 83f0862..d7ebfbc 100644
--- a/car/app/app-automotive/api/public_plus_experimental_current.txt
+++ b/car/app/app-automotive/api/public_plus_experimental_current.txt
@@ -3,7 +3,7 @@
 
   public abstract class BaseCarAppActivity extends androidx.fragment.app.FragmentActivity implements androidx.lifecycle.LifecycleOwner {
     ctor public BaseCarAppActivity();
-    method public void bindToViewModel();
+    method public void bindToViewModel(androidx.car.app.SessionInfo);
     method public android.content.ComponentName? retrieveServiceComponentName();
   }
 
diff --git a/car/app/app-automotive/api/restricted_current.txt b/car/app/app-automotive/api/restricted_current.txt
index cf8af8b..9c3b7b0 100644
--- a/car/app/app-automotive/api/restricted_current.txt
+++ b/car/app/app-automotive/api/restricted_current.txt
@@ -3,7 +3,7 @@
 
   public abstract class BaseCarAppActivity extends androidx.fragment.app.FragmentActivity {
     ctor public BaseCarAppActivity();
-    method public void bindToViewModel();
+    method public void bindToViewModel(androidx.car.app.SessionInfo);
     method public android.content.ComponentName? retrieveServiceComponentName();
   }
 
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
index 9c0574f..13b2553 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
@@ -40,6 +40,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.car.app.SessionInfo;
 import androidx.car.app.activity.renderer.ICarAppActivity;
 import androidx.car.app.activity.renderer.IInsetsListener;
 import androidx.car.app.activity.renderer.IRendererCallback;
@@ -252,7 +253,7 @@
     /**
      * Binds the {@link BaseCarAppActivity} and it's view against the view model.
      */
-    public void bindToViewModel() {
+    public void bindToViewModel(@NonNull SessionInfo sessionInfo) {
         ComponentName serviceComponentName = retrieveServiceComponentName();
         if (serviceComponentName == null) {
             Log.e(LogTags.TAG, "Unspecified service class name");
@@ -261,7 +262,7 @@
         }
 
         CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(getApplication(),
-                serviceComponentName);
+                serviceComponentName, sessionInfo);
         mViewModel = new ViewModelProvider(this, factory).get(CarAppViewModel.class);
         mViewModel.setActivity(this);
         mViewModel.resetState();
@@ -329,9 +330,7 @@
     }
 
     private void onErrorChanged(@Nullable ErrorHandler.ErrorType errorType) {
-        ThreadUtils.runOnMain(() -> {
-            mErrorMessageView.setError(errorType);
-        });
+        ThreadUtils.runOnMain(() -> mErrorMessageView.setError(errorType));
     }
 
     private void onStateChanged(@NonNull CarAppViewModel.State state) {
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
index faf5f42..c5d96a5 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -16,6 +16,10 @@
 
 package androidx.car.app.activity;
 
+import static androidx.car.app.SessionInfo.DISPLAY_TYPE_MAIN;
+
+import static java.lang.System.identityHashCode;
+
 import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.os.Bundle;
@@ -23,6 +27,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.car.app.CarAppService;
+import androidx.car.app.SessionInfo;
 
 /**
  * The class representing a car app activity in the main display.
@@ -76,6 +81,15 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        bindToViewModel();
+
+        String identifier;
+        if (getIntent().getIdentifier() != null) {
+            identifier = getIntent().getIdentifier();
+        } else {
+            identifier = String.valueOf(identityHashCode(this));
+        }
+
+        bindToViewModel(new SessionInfo(DISPLAY_TYPE_MAIN,
+                identifier));
     }
 }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
index 9b64f77..a1737df 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
@@ -30,6 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
+import androidx.car.app.SessionInfo;
 import androidx.car.app.activity.renderer.ICarAppActivity;
 import androidx.car.app.activity.renderer.IInsetsListener;
 import androidx.car.app.activity.renderer.IRendererCallback;
@@ -56,9 +57,12 @@
     private final MutableLiveData mError = new MutableLiveData<>();
     private final MutableLiveData mState = new MutableLiveData<>(State.IDLE);
     private ServiceConnectionManager mServiceConnectionManager;
-    @Nullable private IRendererCallback mIRendererCallback;
-    @Nullable private IInsetsListener mIInsetsListener;
-    @Nullable private Insets mInsets = Insets.NONE;
+    @Nullable
+    private IRendererCallback mIRendererCallback;
+    @Nullable
+    private IInsetsListener mIInsetsListener;
+    @Nullable
+    private Insets mInsets = Insets.NONE;
     private static WeakReference sActivity = new WeakReference<>(null);
 
     /** Possible view states */
@@ -73,14 +77,17 @@
         ERROR,
     }
 
-    public CarAppViewModel(@NonNull Application application, @NonNull ComponentName componentName) {
+    public CarAppViewModel(@NonNull Application application,
+            @NonNull ComponentName componentName, @NonNull SessionInfo sessionInfo) {
         super(application);
 
-        mServiceConnectionManager = new ServiceConnectionManager(application, componentName, this);
+        mServiceConnectionManager = new ServiceConnectionManager(application, componentName,
+                sessionInfo, this);
     }
 
     @VisibleForTesting
-    @NonNull ServiceConnectionManager getServiceConnectionManager() {
+    @NonNull
+    ServiceConnectionManager getServiceConnectionManager() {
         return mServiceConnectionManager;
     }
 
@@ -89,7 +96,8 @@
         mServiceConnectionManager = serviceConnectionManager;
     }
 
-    @NonNull ServiceDispatcher getServiceDispatcher() {
+    @NonNull
+    ServiceDispatcher getServiceDispatcher() {
         return mServiceConnectionManager.getServiceDispatcher();
     }
 
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
index 3c9627d..4ce892c 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.SessionInfo;
 import androidx.lifecycle.ViewModel;
 import androidx.lifecycle.ViewModelProvider;
 
@@ -40,11 +41,13 @@
 
     Application mApplication;
     ComponentName mComponentName;
+    SessionInfo mSessionInfo;
 
     private CarAppViewModelFactory(@NonNull ComponentName componentName,
-            @NonNull Application application) {
+            @NonNull Application application, @NonNull SessionInfo sessionInfo) {
         mComponentName = componentName;
         mApplication = application;
+        mSessionInfo = sessionInfo;
     }
 
     /**
@@ -54,10 +57,10 @@
      */
     @NonNull
     static CarAppViewModelFactory getInstance(Application application,
-            ComponentName componentName) {
+            ComponentName componentName, SessionInfo sessionInfo) {
         CarAppViewModelFactory instance = sInstances.get(componentName);
         if (instance == null) {
-            instance = new CarAppViewModelFactory(componentName, application);
+            instance = new CarAppViewModelFactory(componentName, application, sessionInfo);
             sInstances.put(componentName, instance);
         }
         return instance;
@@ -67,6 +70,6 @@
     @NonNull
     @Override
     public  T create(@NonNull Class modelClass) {
-        return (T) new CarAppViewModel(mApplication, mComponentName);
+        return (T) new CarAppViewModel(mApplication, mComponentName, mSessionInfo);
     }
 }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
index 8d91a334..5d8ff94 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
@@ -36,6 +36,7 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.car.app.HandshakeInfo;
+import androidx.car.app.SessionInfo;
 import androidx.car.app.activity.renderer.ICarAppActivity;
 import androidx.car.app.activity.renderer.IRendererService;
 import androidx.car.app.versioning.CarAppApiLevels;
@@ -58,6 +59,7 @@
 
     final ServiceConnectionListener mListener;
     private final ComponentName mServiceComponentName;
+    private final SessionInfo mSessionInfo;
     private final Context mContext;
     private final ServiceDispatcher mServiceDispatcher;
     private int mDisplayId;
@@ -79,8 +81,10 @@
 
     public ServiceConnectionManager(@NonNull Context context,
             @NonNull ComponentName serviceComponentName,
+            @NonNull SessionInfo sessionInfo,
             @NonNull ServiceConnectionListener listener) {
         mContext = context;
+        mSessionInfo = sessionInfo;
         mListener = listener;
         mServiceComponentName = serviceComponentName;
         mServiceDispatcher = new ServiceDispatcher(listener, this::isBound);
@@ -197,6 +201,7 @@
         }
 
         Intent rendererIntent = new Intent(ACTION_RENDER);
+        SessionInfo.setBindData(rendererIntent, mSessionInfo);
         List resolveInfoList =
                 mContext.getPackageManager()
                         .queryIntentServices(rendererIntent, PackageManager.GET_META_DATA);
@@ -284,7 +289,7 @@
     private boolean updateIntent() {
         ComponentName serviceComponentName = requireNonNull(mServiceComponentName);
         Intent intent = requireNonNull(mIntent);
-
+        SessionInfo.setBindData(intent, mSessionInfo);
         IRendererService service = mRendererService;
         if (service == null) {
             Log.e(LogTags.TAG, "Service dispatcher is not connected");
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index dd0c09e..b7a7fc9 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -19,6 +19,8 @@
 import static android.view.KeyEvent.ACTION_UP;
 import static android.view.KeyEvent.KEYCODE_R;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
@@ -51,6 +53,7 @@
 import android.view.inputmethod.InputConnection;
 
 import androidx.car.app.CarAppService;
+import androidx.car.app.SessionInfo;
 import androidx.car.app.activity.renderer.ICarAppActivity;
 import androidx.car.app.activity.renderer.IInsetsListener;
 import androidx.car.app.activity.renderer.IProxyInputConnection;
@@ -64,7 +67,6 @@
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.test.core.app.ActivityScenario;
-import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,19 +81,20 @@
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 public class CarAppActivityTest {
-    private final ComponentName mRendererComponent = new ComponentName(
-            ApplicationProvider.getApplicationContext(), getClass().getName());
+    public static final String INTENT_IDENTIFIER = "CarAppActivityTest";
+    private final ComponentName mRendererComponent = new ComponentName(getApplicationContext(),
+            getClass().getName());
     private final String mFakeCarAppServiceClass = "com.fake.FakeCarAppService";
     private final ComponentName mFakeCarAppServiceComponent = new ComponentName(
-            ApplicationProvider.getApplicationContext(), mFakeCarAppServiceClass);
+            getApplicationContext(), mFakeCarAppServiceClass);
     private final IRendererService mRenderService = mock(IRendererService.class);
-    private final RenderServiceDelegate mRenderServiceDelegate =
-            new RenderServiceDelegate(mRenderService);
+    private final RenderServiceDelegate mRenderServiceDelegate = new RenderServiceDelegate(
+            mRenderService);
 
     @Before
     public void setup() {
         try {
-            Application app = ApplicationProvider.getApplicationContext();
+            Application app = getApplicationContext();
 
             // Register fake {@code CarAppService}
             PackageManager packageManager = app.getPackageManager();
@@ -105,8 +108,7 @@
             spm.addIntentFilterForService(mRendererComponent,
                     new IntentFilter(CarAppActivity.ACTION_RENDER));
 
-            when(mRenderService.initialize(any(ICarAppActivity.class),
-                    any(ComponentName.class),
+            when(mRenderService.initialize(any(ICarAppActivity.class), any(ComponentName.class),
                     anyInt())).thenReturn(true);
             when(mRenderService.onNewIntent(any(Intent.class), any(ComponentName.class),
                     anyInt())).thenReturn(true);
@@ -122,8 +124,7 @@
     @Test
     public void testRendererInitialization() {
         runOnActivity((scenario, activity) -> {
-            verify(mRenderService, times(1)).initialize(
-                    mRenderServiceDelegate.getCarAppActivity(),
+            verify(mRenderService, times(1)).initialize(mRenderServiceDelegate.getCarAppActivity(),
                     mFakeCarAppServiceComponent, activity.getDisplayId());
             verify(mRenderService, times(1)).onNewIntent(activity.getIntent(),
                     mFakeCarAppServiceComponent, activity.getDisplayId());
@@ -162,12 +163,10 @@
             verify(callback, times(1)).onResume();
 
             // Add a test-specific lifecycle callback to activity.
-            ActivityLifecycleCallbacks activityCallback = mock(
-                    ActivityLifecycleCallbacks.class);
+            ActivityLifecycleCallbacks activityCallback = mock(ActivityLifecycleCallbacks.class);
             activity.registerActivityLifecycleCallbacks(activityCallback);
             // Report service connection error.
-            CarAppViewModel viewModel =
-                    new ViewModelProvider(activity).get(CarAppViewModel.class);
+            CarAppViewModel viewModel = new ViewModelProvider(activity).get(CarAppViewModel.class);
             viewModel.onError(ErrorHandler.ErrorType.HOST_ERROR);
 
             assertThat(activity.isFinishing()).isEqualTo(false);
@@ -197,19 +196,17 @@
         runOnActivity((scenario, activity) -> {
             ServiceConnectionManager serviceConnectionManager =
                     activity.mViewModel.getServiceConnectionManager();
-            ServiceConnection serviceConnection =
-                    spy(serviceConnectionManager.getServiceConnection());
+            ServiceConnection serviceConnection = spy(
+                    serviceConnectionManager.getServiceConnection());
             serviceConnectionManager.setServiceConnection(serviceConnection);
 
             // Destroy activity to force unbind.
             scenario.moveToState(Lifecycle.State.DESTROYED);
 
             // Verify Activity onDestroy even is reported to renderer.
-            verify(mRenderService, times(1)).terminate(
-                    mFakeCarAppServiceComponent);
+            verify(mRenderService, times(1)).terminate(mFakeCarAppServiceComponent);
             // Verify service connection is closed.
-            verify(serviceConnection, times(1)).onServiceDisconnected(
-                    mRendererComponent);
+            verify(serviceConnection, times(1)).onServiceDisconnected(mRendererComponent);
             assertThat(serviceConnectionManager.isBound()).isFalse();
 
         });
@@ -223,8 +220,7 @@
             IRendererCallback rendererCallback = mock(IRendererCallback.class);
 
             ICarAppActivity carAppActivity = mRenderServiceDelegate.getCarAppActivity();
-            carAppActivity.setSurfacePackage(
-                    Bundleable.create(new LegacySurfacePackage(callback)));
+            carAppActivity.setSurfacePackage(Bundleable.create(new LegacySurfacePackage(callback)));
             carAppActivity.registerRendererCallback(rendererCallback);
 
             // Verify back events on the activity are sent to host.
@@ -243,11 +239,9 @@
             int x = 50;
             int y = 50;
             int metaState = 0;
-            MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y,
-                    metaState);
+            MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, metaState);
             activity.mSurfaceView.dispatchTouchEvent(event);
-            ArgumentCaptor argument = ArgumentCaptor.forClass(
-                    MotionEvent.class);
+            ArgumentCaptor argument = ArgumentCaptor.forClass(MotionEvent.class);
             verify(callback, times(1)).onTouchEvent(argument.capture());
             // Compare string representations as equals in MotionEvent checks for same
             // object.
@@ -306,15 +300,13 @@
             View activityContainer = activity.mActivityContainerView;
             View localContentContainer = activity.mLocalContentContainerView;
             Insets systemWindowInsets = Insets.of(10, 20, 30, 40);
-            WindowInsets windowInsets = new WindowInsetsCompat.Builder()
-                    .setInsets(WindowInsetsCompat.Type.systemBars(), systemWindowInsets)
-                    .build()
-                    .toWindowInsets();
+            WindowInsets windowInsets = new WindowInsetsCompat.Builder().setInsets(
+                    WindowInsetsCompat.Type.systemBars(),
+                    systemWindowInsets).build().toWindowInsets();
             activityContainer.onApplyWindowInsets(windowInsets);
 
             // Verify that the host is notified and insets are not handled locally
-            verify(insetsListener).onInsetsChanged(
-                    eq(systemWindowInsets.toPlatformInsets()));
+            verify(insetsListener).onInsetsChanged(eq(systemWindowInsets.toPlatformInsets()));
             assertThat(activityContainer.getPaddingBottom()).isEqualTo(0);
             assertThat(activityContainer.getPaddingTop()).isEqualTo(0);
             assertThat(activityContainer.getPaddingLeft()).isEqualTo(0);
@@ -346,6 +338,62 @@
         });
     }
 
+    @Test
+    public void testLaunchWithIdentifier_passesAlongValues() {
+        ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        Intent newIntent = new Intent(getApplicationContext(), CarAppActivity.class);
+        newIntent.setIdentifier(INTENT_IDENTIFIER);
+        try (ActivityScenario scenario = ActivityScenario.launch(newIntent)) {
+            scenario.onActivity(activity -> {
+                try {
+                    verify(mRenderService, times(1)).initialize(
+                            mRenderServiceDelegate.getCarAppActivity(), mFakeCarAppServiceComponent,
+                            activity.getDisplayId());
+                    verify(mRenderService, times(1)).onNewIntent(intentArgumentCaptor.capture(),
+                            eq(mFakeCarAppServiceComponent), eq(activity.getDisplayId()));
+
+                    Intent intent = intentArgumentCaptor.getValue();
+                    SessionInfo si = new SessionInfo(intent);
+
+                    assertThat(si.getDisplayType()).isEqualTo(SessionInfo.DISPLAY_TYPE_MAIN);
+                    assertThat(intent.getIdentifier()).isEqualTo(si.toString());
+                } catch (Exception e) {
+                    fail(Log.getStackTraceString(e));
+                }
+            });
+
+        }
+
+    }
+
+    @Test
+    public void testLaunchWithoutIdentifier_setsRandomIdValue() {
+        ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        Intent newIntent = new Intent(getApplicationContext(), CarAppActivity.class);
+        try (ActivityScenario scenario = ActivityScenario.launch(newIntent)) {
+            scenario.onActivity(activity -> {
+                try {
+                    verify(mRenderService, times(1)).initialize(
+                            mRenderServiceDelegate.getCarAppActivity(), mFakeCarAppServiceComponent,
+                            activity.getDisplayId());
+                    verify(mRenderService, times(1)).onNewIntent(intentArgumentCaptor.capture(),
+                            eq(mFakeCarAppServiceComponent), eq(activity.getDisplayId()));
+
+                    Intent intent = intentArgumentCaptor.getValue();
+                    SessionInfo si = new SessionInfo(intent);
+
+                    assertThat(si.getDisplayType()).isEqualTo(SessionInfo.DISPLAY_TYPE_MAIN);
+                    assertThat(intent.getIdentifier()).isNotEqualTo(INTENT_IDENTIFIER);
+                    assertThat(intent.getIdentifier()).isEqualTo(si.toString());
+                } catch (Exception e) {
+                    fail(Log.getStackTraceString(e));
+                }
+            });
+
+        }
+
+    }
+
     interface CarActivityAction {
         void accept(ActivityScenario scenario, CarAppActivity activity)
                 throws Exception;
@@ -361,6 +409,7 @@
                     fail(Log.getStackTraceString(e));
                 }
             });
+
         }
     }
 }
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
index 7650a2d..8625a22 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
@@ -16,6 +16,8 @@
 
 package androidx.car.app.activity;
 
+import static androidx.car.app.SessionInfo.DEFAULT_SESSION_INFO;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Application;
@@ -42,10 +44,10 @@
     @Test
     public void getInstance_sameKey_returnsSame() {
         CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
-                TEST_COMPONENT_NAME_1);
+                TEST_COMPONENT_NAME_1, DEFAULT_SESSION_INFO);
 
         CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
-                TEST_COMPONENT_NAME_1);
+                TEST_COMPONENT_NAME_1, DEFAULT_SESSION_INFO);
 
         assertThat(factory1).isEqualTo(factory2);
     }
@@ -53,10 +55,10 @@
     @Test
     public void getInstance_differentKeys_returnsDifferent() {
         CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
-                TEST_COMPONENT_NAME_1);
+                TEST_COMPONENT_NAME_1, DEFAULT_SESSION_INFO);
 
         CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
-                TEST_COMPONENT_NAME_2);
+                TEST_COMPONENT_NAME_2, DEFAULT_SESSION_INFO);
 
         assertThat(factory1).isNotEqualTo(factory2);
     }
@@ -64,7 +66,7 @@
     @Test
     public void create_correctComponentName() {
         CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(mApplication,
-                TEST_COMPONENT_NAME_1);
+                TEST_COMPONENT_NAME_1, DEFAULT_SESSION_INFO);
 
         CarAppViewModel viewModel = factory.create(CarAppViewModel.class);
 
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
index 3c7741f3..353977c 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
@@ -16,6 +16,8 @@
 
 package androidx.car.app.activity;
 
+import static androidx.car.app.SessionInfo.DEFAULT_SESSION_INFO;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -39,7 +41,7 @@
 
 /** Tests for {@link CarAppViewModel} */
 @RunWith(RobolectricTestRunner.class)
-@Config(instrumentedPackages = { "androidx.car.app.activity" })
+@Config(instrumentedPackages = {"androidx.car.app.activity"})
 @DoNotInstrument
 public class CarAppViewModelTest {
     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
@@ -49,7 +51,7 @@
 
     private final Application mApplication = ApplicationProvider.getApplicationContext();
     private CarAppViewModel mCarAppViewModel;
-    private CarAppActivity mCarAppActivity = mock(CarAppActivity.class);
+    private final CarAppActivity mCarAppActivity = mock(CarAppActivity.class);
     private ICarAppActivity mICarAppActivity;
     private ShadowLooper mMainLooper;
     private final ServiceConnectionManager mServiceConnectionManager =
@@ -57,7 +59,8 @@
 
     @Before
     public void setUp() {
-        mCarAppViewModel = new CarAppViewModel(mApplication, TEST_COMPONENT_NAME);
+        mCarAppViewModel = new CarAppViewModel(mApplication, TEST_COMPONENT_NAME,
+                DEFAULT_SESSION_INFO);
         mCarAppViewModel.setActivity(mCarAppActivity);
         mICarAppActivity = mock(ICarAppActivity.class);
         mMainLooper = shadowOf(mApplication.getMainLooper());
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/ResultManagerAutomotiveTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/ResultManagerAutomotiveTest.java
index 6f987f8..ca31699 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/ResultManagerAutomotiveTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/ResultManagerAutomotiveTest.java
@@ -16,6 +16,8 @@
 
 package androidx.car.app.activity;
 
+import static androidx.car.app.SessionInfo.DEFAULT_SESSION_INFO;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -43,19 +45,20 @@
 
 /** Tests for {@link ResultManagerAutomotive}. */
 @RunWith(RobolectricTestRunner.class)
-@Config(instrumentedPackages = { "androidx.car.app.activity" })
+@Config(instrumentedPackages = {"androidx.car.app.activity"})
 @DoNotInstrument
 public class ResultManagerAutomotiveTest {
     private final ComponentName mRendererComponent = new ComponentName(
             ApplicationProvider.getApplicationContext(), getClass().getName());
     private final Application mApplication = ApplicationProvider.getApplicationContext();
-    private CarAppActivity mCarAppActivity = mock(CarAppActivity.class);
-    private ResultManagerAutomotive mResultManager = new ResultManagerAutomotive();
+    private final CarAppActivity mCarAppActivity = mock(CarAppActivity.class);
+    private final ResultManagerAutomotive mResultManager = new ResultManagerAutomotive();
     private CarAppViewModel mCarAppViewModel;
 
     @Before
     public void setUp() {
-        mCarAppViewModel = new CarAppViewModel(mApplication, mRendererComponent);
+        mCarAppViewModel = new CarAppViewModel(mApplication, mRendererComponent,
+                DEFAULT_SESSION_INFO);
     }
 
     @Test
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
index 5bfc022..72c040b 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
@@ -18,11 +18,14 @@
 
 import static android.os.Looper.getMainLooper;
 
+import static androidx.car.app.SessionInfo.DEFAULT_SESSION_INFO;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -38,6 +41,7 @@
 import android.util.Log;
 
 import androidx.car.app.HandshakeInfo;
+import androidx.car.app.SessionInfo;
 import androidx.car.app.activity.renderer.ICarAppActivity;
 import androidx.car.app.activity.renderer.IRendererService;
 import androidx.car.app.serialization.Bundleable;
@@ -47,6 +51,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 import org.robolectric.shadows.ShadowApplication;
@@ -72,7 +77,7 @@
             new RenderServiceDelegate(mRenderService);
     private final CarAppViewModel mViewModel =
             new CarAppViewModel(ApplicationProvider.getApplicationContext(),
-                    mFakeCarAppServiceComponent);
+                    mFakeCarAppServiceComponent, DEFAULT_SESSION_INFO);
     private final ServiceConnectionManager mServiceConnectionManager =
             mViewModel.getServiceConnectionManager();
     private final ShadowLooper mMainLooper = shadowOf(getMainLooper());
@@ -125,7 +130,7 @@
     public void testBind_unbound_bindsToRenderer() {
         setupCarAppActivityForTesting();
         ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
-
+        ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
         mMainLooper.idle();
         try {
@@ -133,12 +138,17 @@
             assertThat(mViewModel.getError().getValue()).isNull();
             verify(mRenderService).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
                     TEST_DISPLAY_ID);
-            verify(mRenderService).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
-                    TEST_DISPLAY_ID);
+            verify(mRenderService).onNewIntent(intentArgumentCaptor.capture(),
+                    eq(mFakeCarAppServiceComponent),
+                    eq(TEST_DISPLAY_ID));
         } catch (RemoteException e) {
             fail(Log.getStackTraceString(e));
         }
         assertThat(mServiceConnectionManager.isBound()).isTrue();
+        assertThat(intentArgumentCaptor.getValue()).isEqualTo(TEST_INTENT);
+        assertThat(intentArgumentCaptor.getValue().getExtras()).isNotNull();
+        assertThat(new SessionInfo(intentArgumentCaptor.getValue())).isEqualTo(
+                DEFAULT_SESSION_INFO);
     }
 
     @Test
diff --git a/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java b/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
index 329f07f..d60e6df 100644
--- a/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
+++ b/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
@@ -27,6 +27,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -48,6 +49,7 @@
         mCarContext.getFakeHost().setMicrophoneInputData(new ByteArrayInputStream(mArr));
     }
 
+    @Ignore // b/234034123
     @Test
     public void readNotStarted_throws() {
         byte[] arr = new byte[AUDIO_CONTENT_BUFFER_SIZE];
@@ -55,6 +57,7 @@
                 AUDIO_CONTENT_BUFFER_SIZE));
     }
 
+    @Ignore // b/234034123
     @Test
     public void readAfterStop_throws() {
         mCarAudioRecord.startRecording();
@@ -65,6 +68,7 @@
                 AUDIO_CONTENT_BUFFER_SIZE));
     }
 
+    @Ignore // b/234034123
     @Test
     public void read() {
         mCarAudioRecord.startRecording();
@@ -75,6 +79,7 @@
         mCarAudioRecord.stopRecording();
     }
 
+    @Ignore // b/234034123
     @Test
     public void readAfterAllRead_returns_negative_1() {
         mCarAudioRecord.startRecording();
@@ -87,6 +92,7 @@
         mCarAudioRecord.stopRecording();
     }
 
+    @Ignore // b/234034123
     @Test
     public void stopRecording_tellsHostToStop() {
         mCarAudioRecord.startRecording();
@@ -98,6 +104,7 @@
         assertThat(mCarContext.getFakeHost().hasToldHostToStopRecording()).isTrue();
     }
 
+    @Ignore // b/234034123
     @Test
     public void hostTellsToStop_noLongerReadsBytes() {
         mCarAudioRecord.startRecording();
diff --git a/car/app/app-samples/helloworld/automotive/github_build.gradle b/car/app/app-samples/helloworld/automotive/github_build.gradle
index 2e30e16..7fe19aa 100644
--- a/car/app/app-samples/helloworld/automotive/github_build.gradle
+++ b/car/app/app-samples/helloworld/automotive/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.helloworld"
         minSdkVersion 29
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -41,6 +41,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.helloworld"
 }
 
 dependencies {
diff --git a/car/app/app-samples/helloworld/common/github_build.gradle b/car/app/app-samples/helloworld/common/github_build.gradle
index 8eb82ea..e5cf456 100644
--- a/car/app/app-samples/helloworld/common/github_build.gradle
+++ b/car/app/app-samples/helloworld/common/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -40,6 +40,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.helloworld.common"
 }
 
 dependencies {
diff --git a/car/app/app-samples/helloworld/mobile/github_build.gradle b/car/app/app-samples/helloworld/mobile/github_build.gradle
index ea2d696..f836662 100644
--- a/car/app/app-samples/helloworld/mobile/github_build.gradle
+++ b/car/app/app-samples/helloworld/mobile/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.helloworld"
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -41,6 +41,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.helloworld"
 }
 
 dependencies {
diff --git a/car/app/app-samples/navigation/automotive/github_build.gradle b/car/app/app-samples/navigation/automotive/github_build.gradle
index 208026e..c794e97 100644
--- a/car/app/app-samples/navigation/automotive/github_build.gradle
+++ b/car/app/app-samples/navigation/automotive/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.navigation"
         minSdkVersion 29
-        targetSdkVersion 31
+        targetSdkVersion 32
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the navigation-mobile version
         versionCode 110
@@ -43,6 +43,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.navigation"
 }
 
 dependencies {
diff --git a/car/app/app-samples/navigation/common/github_build.gradle b/car/app/app-samples/navigation/common/github_build.gradle
index 0b13b40..878aa78 100644
--- a/car/app/app-samples/navigation/common/github_build.gradle
+++ b/car/app/app-samples/navigation/common/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -29,6 +29,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.navigation.common"
 }
 
 dependencies {
diff --git a/car/app/app-samples/navigation/mobile/github_build.gradle b/car/app/app-samples/navigation/mobile/github_build.gradle
index e4aebc6..f65aea6 100644
--- a/car/app/app-samples/navigation/mobile/github_build.gradle
+++ b/car/app/app-samples/navigation/mobile/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.navigation"
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the navigation-automotive version
         versionCode 110
@@ -43,6 +43,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.navigation"
 }
 
 dependencies {
diff --git a/car/app/app-samples/places/automotive/github_build.gradle b/car/app/app-samples/places/automotive/github_build.gradle
index f9b2078..0cade8e 100644
--- a/car/app/app-samples/places/automotive/github_build.gradle
+++ b/car/app/app-samples/places/automotive/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
     defaultConfig {
         applicationId "androidx.car.app.sample.places"
         minSdkVersion 29
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -40,6 +40,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.places"
 }
 
 dependencies {
diff --git a/car/app/app-samples/places/common/github_build.gradle b/car/app/app-samples/places/common/github_build.gradle
index e51f92f..da6b961 100644
--- a/car/app/app-samples/places/common/github_build.gradle
+++ b/car/app/app-samples/places/common/github_build.gradle
@@ -17,10 +17,10 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -28,6 +28,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.places.common"
 }
 
 dependencies {
diff --git a/car/app/app-samples/places/mobile/github_build.gradle b/car/app/app-samples/places/mobile/github_build.gradle
index cf37c23..b93b250 100644
--- a/car/app/app-samples/places/mobile/github_build.gradle
+++ b/car/app/app-samples/places/mobile/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
     defaultConfig {
         applicationId "androidx.car.app.sample.places"
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -40,6 +40,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.places"
 }
 
 dependencies {
diff --git a/car/app/app-samples/showcase/automotive/github_build.gradle b/car/app/app-samples/showcase/automotive/github_build.gradle
index 4622b8c..fe1253d 100644
--- a/car/app/app-samples/showcase/automotive/github_build.gradle
+++ b/car/app/app-samples/showcase/automotive/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.showcase"
         minSdkVersion 29
-        targetSdkVersion 31
+        targetSdkVersion 32
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the showcase-mobile version
         versionCode 110
@@ -45,6 +45,7 @@
     }
 
     testOptions.unitTests.includeAndroidResources true
+    namespace "androidx.car.app.sample.showcase"
 }
 
 dependencies {
diff --git a/car/app/app-samples/showcase/common/github_build.gradle b/car/app/app-samples/showcase/common/github_build.gradle
index 33a90bb..acb2903 100644
--- a/car/app/app-samples/showcase/common/github_build.gradle
+++ b/car/app/app-samples/showcase/common/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
     }
@@ -30,6 +30,7 @@
         targetCompatibility = JavaVersion.VERSION_1_8
         sourceCompatibility = JavaVersion.VERSION_1_8
     }
+    namespace "androidx.car.app.sample.showcase.common"
 }
 
 dependencies {
diff --git a/car/app/app-samples/showcase/mobile/github_build.gradle b/car/app/app-samples/showcase/mobile/github_build.gradle
index 01471df..b3a126d 100644
--- a/car/app/app-samples/showcase/mobile/github_build.gradle
+++ b/car/app/app-samples/showcase/mobile/github_build.gradle
@@ -17,12 +17,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 31
+    compileSdk 32
 
     defaultConfig {
         applicationId "androidx.car.app.sample.showcase"
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 32
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the showcase-automotive version
         versionCode 110
@@ -45,6 +45,7 @@
     }
 
     testOptions.unitTests.includeAndroidResources true
+    namespace "androidx.car.app.sample.showcase"
 }
 
 dependencies {
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 347b66d..a696657a 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -159,11 +159,14 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public class SessionInfo {
     ctor public SessionInfo(int, String);
+    ctor public SessionInfo(android.content.Intent);
     method public int getDisplayType();
     method public String getSessionId();
+    method public static void setBindData(android.content.Intent, androidx.car.app.SessionInfo);
     field public static final androidx.car.app.SessionInfo DEFAULT_SESSION_INFO;
     field public static final int DISPLAY_TYPE_CLUSTER = 1; // 0x1
     field public static final int DISPLAY_TYPE_MAIN = 0; // 0x0
+    field public static final String EXTRA_SESSION_INFO = "androidx.car.app.extra.SESSION_INFO";
   }
 
   public interface SurfaceCallback {
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 0556f7b..d69fc33 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -160,12 +160,15 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public class SessionInfo {
     ctor public SessionInfo(int, String);
+    ctor public SessionInfo(android.content.Intent);
     method public int getDisplayType();
     method public String getSessionId();
     method @androidx.car.app.annotations.ExperimentalCarApi public java.util.Set!>? getSupportedTemplates(int);
+    method public static void setBindData(android.content.Intent, androidx.car.app.SessionInfo);
     field public static final androidx.car.app.SessionInfo DEFAULT_SESSION_INFO;
     field public static final int DISPLAY_TYPE_CLUSTER = 1; // 0x1
     field public static final int DISPLAY_TYPE_MAIN = 0; // 0x0
+    field public static final String EXTRA_SESSION_INFO = "androidx.car.app.extra.SESSION_INFO";
   }
 
   public interface SurfaceCallback {
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 347b66d..a696657a 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -159,11 +159,14 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public class SessionInfo {
     ctor public SessionInfo(int, String);
+    ctor public SessionInfo(android.content.Intent);
     method public int getDisplayType();
     method public String getSessionId();
+    method public static void setBindData(android.content.Intent, androidx.car.app.SessionInfo);
     field public static final androidx.car.app.SessionInfo DEFAULT_SESSION_INFO;
     field public static final int DISPLAY_TYPE_CLUSTER = 1; // 0x1
     field public static final int DISPLAY_TYPE_MAIN = 0; // 0x0
+    field public static final String EXTRA_SESSION_INFO = "androidx.car.app.extra.SESSION_INFO";
   }
 
   public interface SurfaceCallback {
diff --git a/car/app/app/src/main/java/androidx/car/app/SessionInfo.java b/car/app/app/src/main/java/androidx/car/app/SessionInfo.java
index 3176923..8006c178 100644
--- a/car/app/app/src/main/java/androidx/car/app/SessionInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/SessionInfo.java
@@ -17,15 +17,24 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.DoNotInline;
 import androidx.annotation.IntDef;
 import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.car.app.annotations.CarProtocol;
 import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.annotations.RequiresCarApi;
 import androidx.car.app.model.Template;
 import androidx.car.app.navigation.model.NavigationTemplate;
+import androidx.car.app.serialization.Bundleable;
+import androidx.car.app.serialization.BundlerException;
 import androidx.car.app.versioning.CarAppApiLevel;
 import androidx.car.app.versioning.CarAppApiLevels;
 
@@ -41,6 +50,9 @@
 public class SessionInfo {
     private static final char DIVIDER = '/';
 
+    /** The key for a {@link Bundleable} extra containing the {@link SessionInfo} for a bind. */
+    public static final String EXTRA_SESSION_INFO = "androidx.car.app.extra.SESSION_INFO";
+
     /** The primary infotainment display usually in the center column of the vehicle. */
     public static final int DISPLAY_TYPE_MAIN = 0;
 
@@ -101,6 +113,53 @@
         mSessionId = sessionId;
     }
 
+    /**
+     * Creates a new {@link SessionInfo} for a given bind {@code intent}
+     */
+    @SuppressWarnings("deprecation")
+    public SessionInfo(@NonNull Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras == null) {
+            throw new IllegalArgumentException(
+                    "Expected the SessionInfo to be encoded in the bind intent extras, but the "
+                            + "extras were null.");
+        }
+
+        Bundleable sessionInfoBundleable = extras.getParcelable(EXTRA_SESSION_INFO);
+        if (sessionInfoBundleable == null) {
+            throw new IllegalArgumentException(
+                    "Expected the SessionInfo to be encoded in the bind intent extras, but they "
+                            + "couldn't be found in the extras.");
+        }
+
+        try {
+            SessionInfo info = (SessionInfo) sessionInfoBundleable.get();
+            this.mSessionId = info.mSessionId;
+            this.mDisplayType = info.mDisplayType;
+        } catch (BundlerException e) {
+            throw new IllegalArgumentException(
+                    "Expected the SessionInfo to be encoded in the bind intent extras, but they "
+                            + "were encoded improperly", e);
+        }
+    }
+
+    /**
+     * Adds the {@link SessionInfo} and associated identifier on the passed {@code intent} to
+     * pass to this service on bind.
+     */
+    public static void setBindData(@NonNull Intent intent, @NonNull SessionInfo sessionInfo) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            Api29.setIdentifier(intent, sessionInfo.toString());
+        } else {
+            intent.setData(new Uri.Builder().path(sessionInfo.toString()).build());
+        }
+        try {
+            intent.putExtra(EXTRA_SESSION_INFO, Bundleable.create(sessionInfo));
+        } catch (BundlerException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     // Required for Bundler
     private SessionInfo() {
         mSessionId = "main";
@@ -153,4 +212,25 @@
         return this.getSessionId().equals(object.getSessionId())
                 && this.getDisplayType() == object.getDisplayType();
     }
+
+    /** Android Q method calls wrapped in a {@link RequiresApi} class to appease the compiler. */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private static class Api29 {
+        // Not instantiable
+        private Api29() {
+        }
+
+        /** Wrapper for {@link Intent#getIdentifier()}. */
+        @DoNotInline
+        @Nullable
+        static String getIdentifier(@NonNull Intent intent) {
+            return intent.getIdentifier();
+        }
+
+        /** Wrapper for {@link Intent#setIdentifier(String)}. */
+        @DoNotInline
+        static void setIdentifier(@NonNull Intent intent, @NonNull String identifier) {
+            intent.setIdentifier(identifier);
+        }
+    }
 }
diff --git a/car/app/app/src/test/java/androidx/car/app/CarAppBinderTest.java b/car/app/app/src/test/java/androidx/car/app/CarAppBinderTest.java
index 63c5c9c..f21bd8d 100644
--- a/car/app/app/src/test/java/androidx/car/app/CarAppBinderTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarAppBinderTest.java
@@ -51,6 +51,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -196,6 +197,7 @@
     }
 
     @SuppressLint("NewApi")
+    @Ignore // b/238635208
     @Test
     public void onAppCreate_updatesTheConfiguration() {
         Configuration configuration = new Configuration();
@@ -250,6 +252,7 @@
     }
 
     @SuppressLint("NewApi")
+    @Ignore // b/238635208
     @Test
     public void onConfigurationChanged_updatesTheConfiguration() throws RemoteException {
         mCarAppBinder.onAppCreate(mMockCarHost, null, new Configuration(),
diff --git a/car/app/app/src/test/java/androidx/car/app/CarContextTest.java b/car/app/app/src/test/java/androidx/car/app/CarContextTest.java
index 3ea8abb..bb49c17 100644
--- a/car/app/app/src/test/java/androidx/car/app/CarContextTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarContextTest.java
@@ -55,6 +55,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -260,6 +261,7 @@
         assertThat(name).isNull();
     }
 
+    @Ignore // b/238635208
     @Test
     public void onConfigurationChanged_updatesTheConfiguration() {
         Configuration configuration = new Configuration();
diff --git a/car/app/app/src/test/java/androidx/car/app/SessionInfoTest.java b/car/app/app/src/test/java/androidx/car/app/SessionInfoTest.java
index 3712ac8..1c3a502 100644
--- a/car/app/app/src/test/java/androidx/car/app/SessionInfoTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/SessionInfoTest.java
@@ -16,17 +16,24 @@
 
 package androidx.car.app;
 
+import static androidx.car.app.SessionInfo.DEFAULT_SESSION_INFO;
 import static androidx.car.app.SessionInfo.DISPLAY_TYPE_CLUSTER;
 import static androidx.car.app.SessionInfo.DISPLAY_TYPE_MAIN;
+import static androidx.car.app.SessionInfo.EXTRA_SESSION_INFO;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Build;
+
 import androidx.car.app.model.Template;
 import androidx.car.app.versioning.CarAppApiLevels;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Set;
@@ -122,4 +129,50 @@
                 anotherDisplayTypeMainSessionId.toString());
     }
 
+    @Test
+    public void bind_insertsExtra() {
+        Intent intent = new Intent();
+        SessionInfo.setBindData(intent, DEFAULT_SESSION_INFO);
+
+        assertThat(intent.hasExtra(EXTRA_SESSION_INFO));
+    }
+
+    @Test
+    public void bind_from_returnsExpectedValue() {
+        Intent intent = new Intent();
+        SessionInfo.setBindData(intent, DEFAULT_SESSION_INFO);
+
+        SessionInfo info = new SessionInfo(intent);
+
+        assertThat(info).isEqualTo(DEFAULT_SESSION_INFO);
+
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    @Config(sdk = Build.VERSION_CODES.Q)
+    public void setBindData() {
+        SessionInfo testSessionInfo =
+                new SessionInfo(DISPLAY_TYPE_CLUSTER, "a-unique-session-id");
+        Intent intent = new Intent();
+        SessionInfo.setBindData(intent, testSessionInfo);
+
+        SessionInfo resultSessionInfo = new SessionInfo(intent);
+        assertThat(resultSessionInfo).isEqualTo(testSessionInfo);
+        assertThat(intent.getIdentifier()).isEqualTo(testSessionInfo.toString());
+    }
+
+    @Test
+    @Config(sdk = Build.VERSION_CODES.P)
+    public void setBindData_belowQ() {
+        SessionInfo testSessionInfo =
+                new SessionInfo(DISPLAY_TYPE_CLUSTER, "a-unique-session-id");
+        Intent intent = new Intent();
+        SessionInfo.setBindData(intent, testSessionInfo);
+
+        SessionInfo resultSessionInfo = new SessionInfo(intent);
+        assertThat(resultSessionInfo).isEqualTo(testSessionInfo);
+        assertThat(intent.getDataString()).isEqualTo(testSessionInfo.toString());
+    }
+
 }
diff --git a/car/app/app/src/test/java/androidx/car/app/notification/CarNotificationManagerTest.java b/car/app/app/src/test/java/androidx/car/app/notification/CarNotificationManagerTest.java
index 7e55652..46ae9a4 100644
--- a/car/app/app/src/test/java/androidx/car/app/notification/CarNotificationManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/notification/CarNotificationManagerTest.java
@@ -39,6 +39,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -94,6 +95,7 @@
                 mDefaultPendingIntent);
     }
 
+    @Ignore // b/238635208
     @SuppressWarnings("deprecation")
     @Test
     public void updateForCar_embedded_overridesAsExpected() {
@@ -122,6 +124,7 @@
         assertThat(action.title).isEqualTo(EXTENDED_ACTION_TITLE);
     }
 
+    @Ignore // b/238635208
     @SuppressWarnings("deprecation")
     @Test
     public void updateForCar_notEmbedded_returnsTheSame() {
@@ -147,6 +150,7 @@
         assertThat(action.title).isEqualTo(DEFAULT_ACTION_TITLE);
     }
 
+    @Ignore // b/238635208
     @SuppressWarnings("deprecation")
     @Test
     public void updateForCar_notExtended_marksAsExtended()
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index af379cf..784367d 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -36,6 +36,7 @@
     macosX64()
     macosArm64()
     linuxX64()
+    ios()
 
     sourceSets {
         commonMain {
@@ -71,7 +72,8 @@
         targets.all { target ->
             if (target.platformType == KotlinPlatformType.native) {
                 target.compilations["main"].defaultSourceSet {
-                    if (target.konanTarget.family == Family.OSX) {
+                    if (target.konanTarget.family == Family.OSX ||
+                        target.konanTarget.family == Family.IOS) {
                         dependsOn(darwinMain)
                     } else if (target.konanTarget.family == Family.LINUX) {
                         dependsOn(linuxMain)
@@ -127,6 +129,7 @@
         jvm = Publish.SNAPSHOT_AND_RELEASE
         linux = Publish.SNAPSHOT_AND_RELEASE
         mac = Publish.SNAPSHOT_AND_RELEASE
+        ios = Publish.SNAPSHOT_AND_RELEASE
     }
     mavenGroup = LibraryGroups.COLLECTION
     inceptionYear = "2018"
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 44c0a94..ed94995 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
@@ -204,7 +204,7 @@
             project: Project,
             configuration: CompilerConfiguration
         ) {
-            val KOTLIN_VERSION_EXPECTATION = "1.7.0"
+            val KOTLIN_VERSION_EXPECTATION = "1.7.10"
             KotlinCompilerVersion.getVersion()?.let { version ->
                 val suppressKotlinVersionCheck = configuration.get(
                     ComposeConfiguration.SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index b66d0de..7b54efd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -631,7 +631,7 @@
         // soon be _more_ visible, so don't scroll.
         if (!containerShrunk) return
 
-        val focusedChild = focusedChild ?: return
+        val focusedChild = focusedChild?.takeIf { it.isAttached } ?: return
         val focusedBounds = coordinates.localBoundingBoxOf(focusedChild, clipBounds = false)
 
         // In order to check if we need to scroll to bring the focused child into view, it's not
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
index 8c0f965..516f7fa 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
@@ -220,6 +220,7 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Suppress("ComposableLambdaParameterNaming", "ComposableLambdaParameterPosition")
 @Composable
 private fun DemoAppBar(
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
index 62a05db..c6f9984 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
@@ -27,6 +27,7 @@
 import androidx.compose.integration.demos.common.Demo
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.LocalContentColor
@@ -72,6 +73,7 @@
 /**
  * [SmallTopAppBar] with a text field allowing filtering all the demos.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun FilterAppBar(
     filterText: String,
diff --git a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
index e22a36e..deee833 100644
--- a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
+++ b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBarDefaults
@@ -31,6 +32,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.style.TextOverflow
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SpecificationTopAppBar(
     title: String,
@@ -38,7 +40,7 @@
 ) {
     val backgroundColors = TopAppBarDefaults.centerAlignedTopAppBarColors()
     val backgroundColor = backgroundColors.containerColor(
-        scrollFraction = scrollBehavior?.scrollFraction ?: 0f
+        colorTransitionFraction = scrollBehavior?.state?.overlappedFraction ?: 0f
     ).value
     val foregroundColors = TopAppBarDefaults.centerAlignedTopAppBarColors(
         containerColor = Color.Transparent,
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 27ac24e..7a57b3e 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -32,11 +32,6 @@
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1 icons, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1 content);
-    method @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarScrollState rememberTopAppBarScrollState(optional float initialOffsetLimit, optional float initialOffset, optional float initialContentOffset);
   }
 
   public final class BadgeDefaults {
@@ -352,28 +347,6 @@
   public final class IncludeFontPaddingHelper_androidKt {
   }
 
-  @androidx.compose.runtime.Stable public interface ListItemColors {
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State headlineColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State leadingIconColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State overlineColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State supportingColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State trailingIconColor(boolean enabled);
-  }
-
-  public final class ListItemDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
-    method @androidx.compose.runtime.Composable public long getContainerColor();
-    method @androidx.compose.runtime.Composable public long getContentColor();
-    method public float getElevation();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
-    property @androidx.compose.runtime.Composable public final long ContainerColor;
-    property @androidx.compose.runtime.Composable public final long ContentColor;
-    property public final float Elevation;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape Shape;
-    field public static final androidx.compose.material3.ListItemDefaults INSTANCE;
-  }
-
   public final class ListItemKt {
   }
 
@@ -693,10 +666,10 @@
   }
 
   @androidx.compose.runtime.Stable public interface TopAppBarColors {
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float scrollFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float colorTransitionFraction);
   }
 
   public final class TopAppBarDefaults {
@@ -707,34 +680,6 @@
     field public static final androidx.compose.material3.TopAppBarDefaults INSTANCE;
   }
 
-  @androidx.compose.runtime.Stable public interface TopAppBarScrollBehavior {
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
-    method public float getScrollFraction();
-    method public androidx.compose.material3.TopAppBarScrollState getState();
-    property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
-    property public abstract float scrollFraction;
-    property public abstract androidx.compose.material3.TopAppBarScrollState state;
-  }
-
-  @androidx.compose.runtime.Stable public final class TopAppBarScrollState {
-    ctor public TopAppBarScrollState(float offsetLimit, float offset, float contentOffset);
-    method public float getContentOffset();
-    method public float getOffset();
-    method public float getOffsetLimit();
-    method public void setContentOffset(float);
-    method public void setOffset(float);
-    method public void setOffsetLimit(float);
-    property public final float contentOffset;
-    property public final float offset;
-    property public final float offsetLimit;
-    field public static final androidx.compose.material3.TopAppBarScrollState.Companion Companion;
-  }
-
-  public static final class TopAppBarScrollState.Companion {
-    method public androidx.compose.runtime.saveable.Saver getSaver();
-    property public final androidx.compose.runtime.saveable.Saver Saver;
-  }
-
   public final class TouchTargetKt {
   }
 
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 9c4cd3d..2a4019a 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -32,11 +32,11 @@
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1 icons, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1 content);
-    method @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarScrollState rememberTopAppBarScrollState(optional float initialOffsetLimit, optional float initialOffset, optional float initialContentOffset);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarScrollState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api public final class AssistChipDefaults {
@@ -504,7 +504,7 @@
     field public static final androidx.compose.material3.InputChipDefaults INSTANCE;
   }
 
-  @androidx.compose.runtime.Stable public interface ListItemColors {
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface ListItemColors {
     method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(boolean enabled);
     method @androidx.compose.runtime.Composable public androidx.compose.runtime.State headlineColor(boolean enabled);
     method @androidx.compose.runtime.Composable public androidx.compose.runtime.State leadingIconColor(boolean enabled);
@@ -513,7 +513,7 @@
     method @androidx.compose.runtime.Composable public androidx.compose.runtime.State trailingIconColor(boolean enabled);
   }
 
-  public final class ListItemDefaults {
+  @androidx.compose.material3.ExperimentalMaterial3Api public final class ListItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
@@ -945,49 +945,51 @@
   }
 
   @androidx.compose.runtime.Stable public interface TopAppBarColors {
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float scrollFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float colorTransitionFraction);
   }
 
   public final class TopAppBarDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
-    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(androidx.compose.material3.TopAppBarScrollState state, optional kotlin.jvm.functions.Function0 canScroll);
-    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, androidx.compose.material3.TopAppBarScrollState state, optional kotlin.jvm.functions.Function0 canScroll);
+    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0 canScroll);
+    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0 canScroll);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors largeTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors mediumTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
-    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior pinnedScrollBehavior(androidx.compose.material3.TopAppBarScrollState state, optional kotlin.jvm.functions.Function0 canScroll);
+    method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.material3.TopAppBarScrollBehavior pinnedScrollBehavior(androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0 canScroll);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors smallTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     field public static final androidx.compose.material3.TopAppBarDefaults INSTANCE;
   }
 
-  @androidx.compose.runtime.Stable public interface TopAppBarScrollBehavior {
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface TopAppBarScrollBehavior {
     method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
-    method public float getScrollFraction();
-    method public androidx.compose.material3.TopAppBarScrollState getState();
+    method public androidx.compose.material3.TopAppBarState getState();
     property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
-    property public abstract float scrollFraction;
-    property public abstract androidx.compose.material3.TopAppBarScrollState state;
+    property public abstract androidx.compose.material3.TopAppBarState state;
   }
 
-  @androidx.compose.runtime.Stable public final class TopAppBarScrollState {
-    ctor public TopAppBarScrollState(float offsetLimit, float offset, float contentOffset);
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarState {
+    ctor public TopAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
+    method public float getCollapsedFraction();
     method public float getContentOffset();
-    method public float getOffset();
-    method public float getOffsetLimit();
+    method public float getHeightOffset();
+    method public float getHeightOffsetLimit();
+    method public float getOverlappedFraction();
     method public void setContentOffset(float);
-    method public void setOffset(float);
-    method public void setOffsetLimit(float);
+    method public void setHeightOffset(float);
+    method public void setHeightOffsetLimit(float);
+    property public final float collapsedFraction;
     property public final float contentOffset;
-    property public final float offset;
-    property public final float offsetLimit;
-    field public static final androidx.compose.material3.TopAppBarScrollState.Companion Companion;
+    property public final float heightOffset;
+    property public final float heightOffsetLimit;
+    property public final float overlappedFraction;
+    field public static final androidx.compose.material3.TopAppBarState.Companion Companion;
   }
 
-  public static final class TopAppBarScrollState.Companion {
-    method public androidx.compose.runtime.saveable.Saver getSaver();
-    property public final androidx.compose.runtime.saveable.Saver Saver;
+  public static final class TopAppBarState.Companion {
+    method public androidx.compose.runtime.saveable.Saver getSaver();
+    property public final androidx.compose.runtime.saveable.Saver Saver;
   }
 
   public final class TouchTargetKt {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 27ac24e..7a57b3e 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -32,11 +32,6 @@
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1 icons, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1 content);
-    method @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0 title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0 navigationIcon, optional kotlin.jvm.functions.Function1 actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarScrollState rememberTopAppBarScrollState(optional float initialOffsetLimit, optional float initialOffset, optional float initialContentOffset);
   }
 
   public final class BadgeDefaults {
@@ -352,28 +347,6 @@
   public final class IncludeFontPaddingHelper_androidKt {
   }
 
-  @androidx.compose.runtime.Stable public interface ListItemColors {
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State headlineColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State leadingIconColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State overlineColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State supportingColor(boolean enabled);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State trailingIconColor(boolean enabled);
-  }
-
-  public final class ListItemDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
-    method @androidx.compose.runtime.Composable public long getContainerColor();
-    method @androidx.compose.runtime.Composable public long getContentColor();
-    method public float getElevation();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
-    property @androidx.compose.runtime.Composable public final long ContainerColor;
-    property @androidx.compose.runtime.Composable public final long ContentColor;
-    property public final float Elevation;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape Shape;
-    field public static final androidx.compose.material3.ListItemDefaults INSTANCE;
-  }
-
   public final class ListItemKt {
   }
 
@@ -693,10 +666,10 @@
   }
 
   @androidx.compose.runtime.Stable public interface TopAppBarColors {
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float scrollFraction);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float scrollFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State actionIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State containerColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State navigationIconContentColor(float colorTransitionFraction);
+    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State titleContentColor(float colorTransitionFraction);
   }
 
   public final class TopAppBarDefaults {
@@ -707,34 +680,6 @@
     field public static final androidx.compose.material3.TopAppBarDefaults INSTANCE;
   }
 
-  @androidx.compose.runtime.Stable public interface TopAppBarScrollBehavior {
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
-    method public float getScrollFraction();
-    method public androidx.compose.material3.TopAppBarScrollState getState();
-    property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
-    property public abstract float scrollFraction;
-    property public abstract androidx.compose.material3.TopAppBarScrollState state;
-  }
-
-  @androidx.compose.runtime.Stable public final class TopAppBarScrollState {
-    ctor public TopAppBarScrollState(float offsetLimit, float offset, float contentOffset);
-    method public float getContentOffset();
-    method public float getOffset();
-    method public float getOffsetLimit();
-    method public void setContentOffset(float);
-    method public void setOffset(float);
-    method public void setOffsetLimit(float);
-    property public final float contentOffset;
-    property public final float offset;
-    property public final float offsetLimit;
-    field public static final androidx.compose.material3.TopAppBarScrollState.Companion Companion;
-  }
-
-  public static final class TopAppBarScrollState.Companion {
-    method public androidx.compose.runtime.saveable.Saver getSaver();
-    property public final androidx.compose.runtime.saveable.Saver Saver;
-  }
-
   public final class TouchTargetKt {
   }
 
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
index 9cb326c..72bcdfc 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
@@ -29,6 +29,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
@@ -49,6 +50,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CatalogTopAppBar(
     title: String,
@@ -67,7 +69,7 @@
     var moreMenuExpanded by remember { mutableStateOf(false) }
     val backgroundColors = TopAppBarDefaults.smallTopAppBarColors()
     val backgroundColor = backgroundColors.containerColor(
-        scrollFraction = scrollBehavior?.scrollFraction ?: 0f
+        colorTransitionFraction = scrollBehavior?.state?.overlappedFraction ?: 0f
     ).value
     val foregroundColors = TopAppBarDefaults.smallTopAppBarColors(
         containerColor = Color.Transparent,
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
index 93d2bcb..c656ac6 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
@@ -27,6 +27,9 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 
 @Sampled
 @Composable
@@ -34,7 +37,10 @@
     var sliderPosition by remember { mutableStateOf(0f) }
     Column {
         Text(text = sliderPosition.toString())
-        Slider(value = sliderPosition, onValueChange = { sliderPosition = it })
+        Slider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it })
     }
 }
 
@@ -45,6 +51,7 @@
     Column {
         Text(text = sliderPosition.toString())
         Slider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
             value = sliderPosition,
             onValueChange = { sliderPosition = it },
             valueRange = 0f..100f,
@@ -65,6 +72,7 @@
     Column {
         Text(text = sliderPosition.toString())
         RangeSlider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
             value = sliderPosition,
             onValueChange = { sliderPosition = it },
             valueRange = 0f..100f,
@@ -84,6 +92,7 @@
     Column {
         Text(text = sliderPosition.toString())
         RangeSlider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
             steps = 5,
             value = sliderPosition,
             onValueChange = { sliderPosition = it },
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index 1560060..1076bf6 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -76,6 +76,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalMaterial3Api::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class AppBarTest {
@@ -177,11 +178,11 @@
                     navigationIconColor = LocalContentColor.current
                     expectedNavigationIconColor =
                         TopAppBarDefaults.smallTopAppBarColors()
-                            .navigationIconContentColor(scrollFraction = 0f).value
-                    // scrollFraction = 0f to indicate no scroll.
+                            .navigationIconContentColor(colorTransitionFraction = 0f).value
+                    // fraction = 0f to indicate no scroll.
                     expectedContainerColor = TopAppBarDefaults
                         .smallTopAppBarColors()
-                        .containerColor(scrollFraction = 0f)
+                        .containerColor(colorTransitionFraction = 0f)
                         .value
                 },
                 title = {
@@ -189,7 +190,7 @@
                     titleColor = LocalContentColor.current
                     expectedTitleColor = TopAppBarDefaults
                         .smallTopAppBarColors()
-                        .titleContentColor(scrollFraction = 0f)
+                        .titleContentColor(colorTransitionFraction = 0f)
                         .value
                 },
                 actions = {
@@ -197,7 +198,7 @@
                     actionsColor = LocalContentColor.current
                     expectedActionsColor = TopAppBarDefaults
                         .smallTopAppBarColors()
-                        .actionIconContentColor(scrollFraction = 0f)
+                        .actionIconContentColor(colorTransitionFraction = 0f)
                         .value
                 }
             )
@@ -225,10 +226,10 @@
                 modifier = Modifier.testTag(TopAppBarTestTag),
                 title = {
                     Text("Title", Modifier.testTag(TitleTestTag))
-                    // scrollFraction = 1f to indicate a scroll.
+                    // fraction = 1f to indicate a scroll.
                     expectedScrolledContainerColor =
                         TopAppBarDefaults.smallTopAppBarColors()
-                            .containerColor(scrollFraction = 1f).value
+                            .containerColor(colorTransitionFraction = 1f).value
                 },
                 scrollBehavior = scrollBehavior
             )
@@ -247,13 +248,13 @@
     @Test
     fun smallTopAppBar_scrolledPositioning() {
         lateinit var scrollBehavior: TopAppBarScrollBehavior
-        val scrollOffsetDp = 20.dp
-        var scrollOffsetPx = 0f
+        val scrollHeightOffsetDp = 20.dp
+        var scrollHeightOffsetPx = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             scrollBehavior =
                 TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarScrollState())
-            scrollOffsetPx = with(LocalDensity.current) { scrollOffsetDp.toPx() }
+            scrollHeightOffsetPx = with(LocalDensity.current) { scrollHeightOffsetDp.toPx() }
             SmallTopAppBar(
                 modifier = Modifier.testTag(TopAppBarTestTag),
                 title = { Text("Title", Modifier.testTag(TitleTestTag)) },
@@ -263,12 +264,12 @@
 
         // Simulate scrolled content.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -scrollOffsetPx
-            scrollBehavior.state.contentOffset = -scrollOffsetPx
+            scrollBehavior.state.heightOffset = -scrollHeightOffsetPx
+            scrollBehavior.state.contentOffset = -scrollHeightOffsetPx
         }
         rule.waitForIdle()
         rule.onNodeWithTag(TopAppBarTestTag)
-            .assertHeightIsEqualTo(TopAppBarSmallTokens.ContainerHeight - scrollOffsetDp)
+            .assertHeightIsEqualTo(TopAppBarSmallTokens.ContainerHeight - scrollHeightOffsetDp)
     }
 
     @Test
@@ -390,25 +391,25 @@
                     navigationIconColor = LocalContentColor.current
                     expectedNavigationIconColor =
                         TopAppBarDefaults.centerAlignedTopAppBarColors()
-                            .navigationIconContentColor(scrollFraction = 0f).value
-                    // scrollFraction = 0f to indicate no scroll.
+                            .navigationIconContentColor(colorTransitionFraction = 0f).value
+                    // fraction = 0f to indicate no scroll.
                     expectedContainerColor =
                         TopAppBarDefaults.centerAlignedTopAppBarColors()
-                            .containerColor(scrollFraction = 0f).value
+                            .containerColor(colorTransitionFraction = 0f).value
                 },
                 title = {
                     Text("Title", Modifier.testTag(TitleTestTag))
                     titleColor = LocalContentColor.current
                     expectedTitleColor =
                         TopAppBarDefaults.centerAlignedTopAppBarColors()
-                            .titleContentColor(scrollFraction = 0f).value
+                            .titleContentColor(colorTransitionFraction = 0f).value
                 },
                 actions = {
                     FakeIcon(Modifier.testTag(ActionsTestTag))
                     actionsColor = LocalContentColor.current
                     expectedActionsColor =
                         TopAppBarDefaults.centerAlignedTopAppBarColors()
-                            .actionIconContentColor(scrollFraction = 0f).value
+                            .actionIconContentColor(colorTransitionFraction = 0f).value
                 }
             )
         }
@@ -436,10 +437,10 @@
                 modifier = Modifier.testTag(TopAppBarTestTag),
                 title = {
                     Text("Title", Modifier.testTag(TitleTestTag))
-                    // scrollFraction = 1f to indicate a scroll.
+                    // fraction = 1f to indicate a scroll.
                     expectedScrolledContainerColor =
                         TopAppBarDefaults.centerAlignedTopAppBarColors()
-                            .containerColor(scrollFraction = 1f).value
+                            .containerColor(colorTransitionFraction = 1f).value
                 },
                 scrollBehavior = scrollBehavior
             )
@@ -725,25 +726,25 @@
     @Test
     fun state_restoresTopAppBarScrollState() {
         val restorationTester = StateRestorationTester(rule)
-        var topAppBarScrollState: TopAppBarScrollState? = null
+        var topAppBarState: TopAppBarState? = null
         restorationTester.setContent {
-            topAppBarScrollState = rememberTopAppBarScrollState()
+            topAppBarState = rememberTopAppBarScrollState()
         }
 
         rule.runOnIdle {
-            topAppBarScrollState!!.offsetLimit = -350f
-            topAppBarScrollState!!.offset = -300f
-            topAppBarScrollState!!.contentOffset = -550f
+            topAppBarState!!.heightOffsetLimit = -350f
+            topAppBarState!!.heightOffset = -300f
+            topAppBarState!!.contentOffset = -550f
         }
 
-        topAppBarScrollState = null
+        topAppBarState = null
 
         restorationTester.emulateSavedInstanceStateRestore()
 
         rule.runOnIdle {
-            assertThat(topAppBarScrollState!!.offsetLimit).isEqualTo(-350f)
-            assertThat(topAppBarScrollState!!.offset).isEqualTo(-300f)
-            assertThat(topAppBarScrollState!!.contentOffset).isEqualTo(-550f)
+            assertThat(topAppBarState!!.heightOffsetLimit).isEqualTo(-350f)
+            assertThat(topAppBarState!!.heightOffset).isEqualTo(-300f)
+            assertThat(topAppBarState!!.contentOffset).isEqualTo(-550f)
         }
     }
 
@@ -757,9 +758,9 @@
                         BottomAppBarDefaults.FloatingActionButton(
                             onClick = { /* do something */ }
                         ) {
-                        Icon(Icons.Filled.Add, "Localized description")
-                    }
-                })
+                            Icon(Icons.Filled.Add, "Localized description")
+                        }
+                    })
             }
             .assertHeightIsEqualTo(BottomAppBarTokens.ContainerHeight)
             .assertWidthIsEqualTo(rule.rootWidth())
@@ -1004,8 +1005,8 @@
     ) {
         val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
         val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
-        var partiallyCollapsedOffsetPx = 0f
-        var fullyCollapsedOffsetPx = 0f
+        var partiallyCollapsedHeightOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
         lateinit var scrollBehavior: TopAppBarScrollBehavior
         rule.setMaterialContent(lightColorScheme()) {
             scrollBehavior =
@@ -1014,8 +1015,8 @@
                     rememberTopAppBarScrollState()
                 )
             with(LocalDensity.current) {
-                partiallyCollapsedOffsetPx = partiallyCollapsedOffsetDp.toPx()
-                fullyCollapsedOffsetPx = fullyCollapsedOffsetDp.toPx()
+                partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
             }
 
             content(scrollBehavior)
@@ -1023,8 +1024,8 @@
 
         // Simulate a partially collapsed app bar.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -partiallyCollapsedOffsetPx
-            scrollBehavior.state.contentOffset = -partiallyCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
         rule.onNodeWithTag(TopAppBarTestTag)
@@ -1032,10 +1033,10 @@
 
         // Simulate a fully collapsed app bar.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -fullyCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
             // Simulate additional content scroll beyond the max offset scroll.
             scrollBehavior.state.contentOffset =
-                -fullyCollapsedOffsetPx - partiallyCollapsedOffsetPx
+                -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
         // Check that the app bar collapsed to its min height.
@@ -1061,8 +1062,8 @@
     ) {
         val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
         val oneThirdCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
-        var fullyCollapsedOffsetPx = 0f
-        var oneThirdCollapsedOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
+        var oneThirdCollapsedHeightOffsetPx = 0f
         var fullyCollapsedContainerColor: Color = Color.Unspecified
         var oneThirdCollapsedContainerColor: Color = Color.Unspecified
         var titleContentColor: Color = Color.Unspecified
@@ -1077,20 +1078,20 @@
             // current content color settings are the same.
             oneThirdCollapsedContainerColor =
                 TopAppBarDefaults.mediumTopAppBarColors()
-                    .containerColor(scrollFraction = 1 / 3f).value
+                    .containerColor(colorTransitionFraction = 1 / 3f).value
             fullyCollapsedContainerColor =
                 TopAppBarDefaults.mediumTopAppBarColors()
-                    .containerColor(scrollFraction = 1f).value
+                    .containerColor(colorTransitionFraction = 1f).value
 
             // Resolve the title's content color. The default implementation returns the same color
-            // regardless of the scrollFraction, and the color is applied later with alpha.
+            // regardless of the fraction, and the color is applied later with alpha.
             titleContentColor =
                 TopAppBarDefaults.mediumTopAppBarColors()
-                    .titleContentColor(scrollFraction = 1f).value
+                    .titleContentColor(colorTransitionFraction = 1f).value
 
             with(LocalDensity.current) {
-                oneThirdCollapsedOffsetPx = oneThirdCollapsedOffsetDp.toPx()
-                fullyCollapsedOffsetPx = fullyCollapsedOffsetDp.toPx()
+                oneThirdCollapsedHeightOffsetPx = oneThirdCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
             }
 
             content(scrollBehavior)
@@ -1105,8 +1106,8 @@
 
         // Simulate 1/3 collapsed content.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -oneThirdCollapsedOffsetPx
-            scrollBehavior.state.contentOffset = -oneThirdCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -oneThirdCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -oneThirdCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
         rule.onNodeWithTag(TopAppBarTestTag).captureToImage()
@@ -1127,8 +1128,8 @@
 
         // Simulate fully collapsed content.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -fullyCollapsedOffsetPx
-            scrollBehavior.state.contentOffset = -fullyCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -fullyCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
         rule.onNodeWithTag(TopAppBarTestTag).captureToImage()
@@ -1157,8 +1158,8 @@
     ) {
         val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
         val oneThirdCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
-        var fullyCollapsedOffsetPx = 0f
-        var oneThirdCollapsedOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
+        var oneThirdCollapsedHeightOffsetPx = 0f
         lateinit var scrollBehavior: TopAppBarScrollBehavior
         rule.setMaterialContent(lightColorScheme()) {
             scrollBehavior =
@@ -1167,8 +1168,8 @@
                     rememberTopAppBarScrollState()
                 )
             with(LocalDensity.current) {
-                oneThirdCollapsedOffsetPx = oneThirdCollapsedOffsetDp.toPx()
-                fullyCollapsedOffsetPx = fullyCollapsedOffsetDp.toPx()
+                oneThirdCollapsedHeightOffsetPx = oneThirdCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
             }
 
             content(scrollBehavior)
@@ -1180,8 +1181,8 @@
 
         // Simulate 1/3 collapsed content.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -oneThirdCollapsedOffsetPx
-            scrollBehavior.state.contentOffset = -oneThirdCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -oneThirdCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -oneThirdCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
 
@@ -1190,8 +1191,8 @@
 
         // Simulate fully collapsed content.
         rule.runOnIdle {
-            scrollBehavior.state.offset = -fullyCollapsedOffsetPx
-            scrollBehavior.state.contentOffset = -fullyCollapsedOffsetPx
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -fullyCollapsedHeightOffsetPx
         }
         rule.waitForIdle()
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index 3fc7f79d..03aedbe 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -111,6 +111,7 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@ExperimentalMaterial3Api
 @Composable
 fun SmallTopAppBar(
     title: @Composable () -> Unit,
@@ -160,6 +161,7 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@ExperimentalMaterial3Api
 @Composable
 fun CenterAlignedTopAppBar(
     title: @Composable () -> Unit,
@@ -211,6 +213,7 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@ExperimentalMaterial3Api
 @Composable
 fun MediumTopAppBar(
     title: @Composable () -> Unit,
@@ -265,6 +268,7 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@ExperimentalMaterial3Api
 @Composable
 fun LargeTopAppBar(
     title: @Composable () -> Unit,
@@ -329,28 +333,28 @@
     tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
     contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
 ) = BottomAppBar(
-        modifier = modifier,
-        containerColor = containerColor,
-        contentColor = contentColor,
-        tonalElevation = tonalElevation,
-        contentPadding = contentPadding
-    ) {
-        icons()
-        if (floatingActionButton != null) {
-            Spacer(Modifier.weight(1f, true))
-            Box(
-                Modifier
-                    .fillMaxHeight()
-                    .padding(
-                        top = FABVerticalPadding,
-                        end = FABHorizontalPadding
-                    ),
-                contentAlignment = Alignment.TopStart
-            ) {
-                floatingActionButton()
-            }
+    modifier = modifier,
+    containerColor = containerColor,
+    contentColor = contentColor,
+    tonalElevation = tonalElevation,
+    contentPadding = contentPadding
+) {
+    icons()
+    if (floatingActionButton != null) {
+        Spacer(Modifier.weight(1f, true))
+        Box(
+            Modifier
+                .fillMaxHeight()
+                .padding(
+                    top = FABVerticalPadding,
+                    end = FABHorizontalPadding
+                ),
+            contentAlignment = Alignment.TopStart
+        ) {
+            floatingActionButton()
         }
     }
+}
 
 /**
  * Material Design bottom app bar.
@@ -413,33 +417,21 @@
  * @see [TopAppBarDefaults.enterAlwaysScrollBehavior]
  * @see [TopAppBarDefaults.exitUntilCollapsedScrollBehavior]
  */
+@ExperimentalMaterial3Api
 @Stable
 interface TopAppBarScrollBehavior {
 
     /**
-     * A [TopAppBarScrollState] that is attached to this behavior and is read and updated when
-     * scrolling happens.
+     * A [TopAppBarState] that is attached to this behavior and is read and updated when scrolling
+     * happens.
      */
-    val state: TopAppBarScrollState
+    val state: TopAppBarState
 
     /**
      * A [NestedScrollConnection] that should be attached to a [Modifier.nestedScroll] in order to
      * keep track of the scroll events.
      */
     val nestedScrollConnection: NestedScrollConnection
-
-    /**
-     * Returns the top app bar's current scroll fraction.
-     *
-     * A scrollFraction is a value between `0.0` to `1.0` that provides a percentage of the app
-     * bar scroll position when the content is scrolled. `0.0` represents an expanded app bar,
-     * while `1.0` represents a collapsed one (e.g. the app bar is scrolled to its target offset).
-     * Note that this value will be updated on scroll even if the [TopAppBarScrollState.offset] is
-     * pinned to a specific value (see [TopAppBarDefaults.pinnedScrollBehavior]). In this case a
-     * value of 1.0 represents that the scroll value has exceeded the height of the pinned app bar,
-     * as if the app bar was collapsing.
-     */
-    val scrollFraction: Float
 }
 
 /**
@@ -451,44 +443,56 @@
 @Stable
 interface TopAppBarColors {
     /**
-     * Represents the container color used for the top app bar, depending on whether the app bar is
-     * scrolled, and the percentage of its area that is scrolled.
+     * Represents the container color used for the top app bar.
      *
-     * @param scrollFraction the scroll percentage of the top app bar (0.0 when the app bar is
-     * considered expanded to 1.0 when the app bar is scrolled to its target offset)
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
      */
     @Composable
-    fun containerColor(scrollFraction: Float): State
+    fun containerColor(colorTransitionFraction: Float): State
 
     /**
-     * Represents the content color used for the top app bar's navigation icon depending on whether
-     * the app bar is scrolled, and the percentage of its area that is scrolled.
+     * Represents the content color used for the top app bar's navigation icon.
      *
-     * @param scrollFraction the scroll percentage of the top app bar (0.0 when the app bar is
-     * considered expanded to 1.0 when the app bar is scrolled to its target offset)
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
      */
     @Composable
-    fun navigationIconContentColor(scrollFraction: Float): State
+    fun navigationIconContentColor(colorTransitionFraction: Float): State
 
     /**
-     * Represents the content color used for the top app bar's title depending on whether the app
-     * bar is scrolled, and the percentage of its area that is scrolled.
+     * Represents the content color used for the top app bar's title.
      *
-     * @param scrollFraction the scroll percentage of the top app bar (0.0 when the app bar is
-     * considered expanded to 1.0 when the app bar is scrolled to its target offset)
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
      */
     @Composable
-    fun titleContentColor(scrollFraction: Float): State
+    fun titleContentColor(colorTransitionFraction: Float): State
 
     /**
-     * Represents the content color used for the top app bar's action icons depending on whether the
-     * app bar is scrolled, and the percentage of its area that is scrolled.
+     * Represents the content color used for the top app bar's action icons.
      *
-     * @param scrollFraction the scroll percentage of the top app bar (0.0 when the app bar is
-     * considered expanded to 1.0 when the app bar is scrolled to its target offset)
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
      */
     @Composable
-    fun actionIconContentColor(scrollFraction: Float): State
+    fun actionIconContentColor(colorTransitionFraction: Float): State
 }
 
 /** Contains default values used for the top app bar implementations. */
@@ -654,7 +658,7 @@
 
     /**
      * Returns a pinned [TopAppBarScrollBehavior] that tracks nested-scroll callbacks and
-     * updates its [TopAppBarScrollState.contentOffset] accordingly.
+     * updates its [TopAppBarState.contentOffset] accordingly.
      *
      * @param state the state object to be used to control or observe the top app bar's scroll
      * state. See also [rememberTopAppBarScrollState] to create a state that is remembered across
@@ -664,7 +668,7 @@
      */
     @ExperimentalMaterial3Api
     fun pinnedScrollBehavior(
-        state: TopAppBarScrollState,
+        state: TopAppBarState,
         canScroll: () -> Boolean = { true }
     ): TopAppBarScrollBehavior = PinnedScrollBehavior(state, canScroll)
 
@@ -681,7 +685,7 @@
      */
     @ExperimentalMaterial3Api
     fun enterAlwaysScrollBehavior(
-        state: TopAppBarScrollState,
+        state: TopAppBarState,
         canScroll: () -> Boolean = { true }
     ): TopAppBarScrollBehavior = EnterAlwaysScrollBehavior(state, canScroll)
 
@@ -706,7 +710,7 @@
     @ExperimentalMaterial3Api
     fun exitUntilCollapsedScrollBehavior(
         decayAnimationSpec: DecayAnimationSpec,
-        state: TopAppBarScrollState,
+        state: TopAppBarState,
         canScroll: () -> Boolean = { true }
     ): TopAppBarScrollBehavior =
         ExitUntilCollapsedScrollBehavior(
@@ -717,82 +721,118 @@
 }
 
 /**
- * Creates a [TopAppBarScrollState] that is remembered across compositions.
+ * Creates a [TopAppBarState] that is remembered across compositions.
  *
- * Changes to the provided initial values will **not** result in the state being recreated or
- * changed in any way if it has already been created.
- *
- * @param initialOffsetLimit the initial value for [TopAppBarScrollState.offsetLimit], which
- * represents the offset that a top app bar is allowed to scroll when the scrollable content is
- * scrolled
- * @param initialOffset the initial value for [TopAppBarScrollState.offset]. The initial offset
- * should be between zero and [initialOffsetLimit].
- * @param initialContentOffset the initial value for [TopAppBarScrollState.contentOffset]
+ * @param initialHeightOffsetLimit the initial value for [TopAppBarState.heightOffsetLimit],
+ * which represents the pixel limit that a top app bar is allowed to collapse when the scrollable
+ * content is scrolled
+ * @param initialHeightOffset the initial value for [TopAppBarState.heightOffset]. The initial
+ * offset height offset should be between zero and [initialHeightOffsetLimit].
+ * @param initialContentOffset the initial value for [TopAppBarState.contentOffset]
  */
+@ExperimentalMaterial3Api
 @Composable
 fun rememberTopAppBarScrollState(
-    initialOffsetLimit: Float = -Float.MAX_VALUE,
-    initialOffset: Float = 0f,
+    initialHeightOffsetLimit: Float = -Float.MAX_VALUE,
+    initialHeightOffset: Float = 0f,
     initialContentOffset: Float = 0f
-): TopAppBarScrollState {
-    return rememberSaveable(saver = TopAppBarScrollState.Saver) {
-        TopAppBarScrollState(
-            initialOffsetLimit,
-            initialOffset,
+): TopAppBarState {
+    return rememberSaveable(saver = TopAppBarState.Saver) {
+        TopAppBarState(
+            initialHeightOffsetLimit,
+            initialHeightOffset,
             initialContentOffset
         )
     }
 }
 
 /**
- * A state object that can be hoisted to control and observe the top app bar scroll state. The state
- * is read and updated by a [TopAppBarScrollBehavior] implementation.
+ * A state object that can be hoisted to control and observe the top app bar state. The state is
+ * read and updated by a [TopAppBarScrollBehavior] implementation.
  *
- * In most cases, this will be created via [rememberTopAppBarScrollState].
+ * In most cases, this state will be created via [rememberTopAppBarScrollState].
  *
- * @param offsetLimit the initial value for [TopAppBarScrollState.offsetLimit]
- * @param offset the initial value for [TopAppBarScrollState.offset]
- * @param contentOffset the initial value for [TopAppBarScrollState.contentOffset]
+ * @param initialHeightOffsetLimit the initial value for [TopAppBarState.heightOffsetLimit]
+ * @param initialHeightOffset the initial value for [TopAppBarState.heightOffset]
+ * @param initialContentOffset the initial value for [TopAppBarState.contentOffset]
  */
+@ExperimentalMaterial3Api
 @Stable
-class TopAppBarScrollState(offsetLimit: Float, offset: Float, contentOffset: Float) {
+class TopAppBarState(
+    initialHeightOffsetLimit: Float,
+    initialHeightOffset: Float,
+    initialContentOffset: Float
+) {
 
     /**
-     * The top app bar's offset limit in pixels, which represents the offset that a top app bar is
-     * allowed to scroll when the scrollable content is scrolled.
+     * The top app bar's height offset limit in pixels, which represents the limit that a top app
+     * bar is allowed to collapse to.
      *
-     * This limit is represented by a negative [Float], and used to coerce the [offset] value when
-     * the content is scrolled.
+     * Use this limit to coerce the [heightOffset] value when it's updated.
      */
-    var offsetLimit by mutableStateOf(offsetLimit)
+    var heightOffsetLimit by mutableStateOf(initialHeightOffsetLimit)
 
     /**
-     * The top app bar's current offset in pixels.
+     * The top app bar's current height offset in pixels. The height offset is applied to the fixed
+     * height of the app bar to control the displayed height when content is being scrolled.
      *
-     * The offset is usually between zero and the [offsetLimit].
+     * This value is intended to be coerced between zero and [heightOffsetLimit].
      */
-    var offset by mutableStateOf(offset)
+    var heightOffset by mutableStateOf(initialHeightOffset)
 
     /**
-     * The current content offset that is updated when the nested scroll connection consumes scroll
-     * events.
+     * The total offset of the content scrolled under the top app bar.
      *
-     * A common behavior implementation would update this value to be the sum of all
+     * The content offset is used to compute the [overlappedFraction], which can later be read
+     * by an implementation.
+     *
+     * This value is updated by a [TopAppBarScrollBehavior] whenever a nested scroll connection
+     * consumes scroll events. A common implementation would update the value to be the sum of all
      * [NestedScrollConnection.onPostScroll] `consumed.y` values.
      */
-    var contentOffset by mutableStateOf(contentOffset)
+    var contentOffset by mutableStateOf(initialContentOffset)
+
+    /**
+     * A value that represents the collapsed height percentage of the app bar.
+     *
+     * A `0.0` represents a fully expanded bar, and `1.0` represents a fully collapsed bar (computed
+     * as [heightOffset] / [heightOffsetLimit]).
+     */
+    val collapsedFraction: Float
+        get() = if (heightOffsetLimit != 0f) {
+            heightOffset / heightOffsetLimit
+        } else {
+            0f
+        }
+
+    /**
+     * A value that represents the percentage of the app bar area that is overlapping with the
+     * content scrolled behind it.
+     *
+     * A `0.0` indicates that the app bar does not overlap any content, while `1.0` indicates that
+     * the entire visible app bar area overlaps the scrolled content.
+     */
+    val overlappedFraction: Float
+        get() = if (heightOffsetLimit != 0f) {
+            1 - ((heightOffsetLimit - contentOffset).coerceIn(
+                minimumValue = heightOffsetLimit,
+                maximumValue = 0f
+            ) / heightOffsetLimit)
+        } else {
+            0f
+        }
 
     companion object {
         /**
-         * The default [Saver] implementation for [TopAppBarScrollState].
+         * The default [Saver] implementation for [TopAppBarState].
          */
-        val Saver: Saver = listSaver(
-            save = { listOf(it.offsetLimit, it.offset, it.contentOffset) },
+        val Saver: Saver = listSaver(
+            save = { listOf(it.heightOffsetLimit, it.heightOffset, it.contentOffset) },
             restore = {
-                TopAppBarScrollState(
-                    offsetLimit = it[0],
-                    offset = it[1],
-                    contentOffset = it[2]
+                TopAppBarState(
+                    initialHeightOffsetLimit = it[0],
+                    initialHeightOffset = it[1],
+                    initialContentOffset = it[2]
                 )
             }
         )
@@ -829,17 +869,20 @@
 
         @Composable
         override fun shadowElevation(interactionSource: InteractionSource) = elevation
+
         @Composable
         override fun tonalElevation(interactionSource: InteractionSource) = elevation
     }
 
     /** The color of a [BottomAppBar]'s [FloatingActionButton] */
-    val FloatingActionButtonContainerColor: Color @Composable get() =
-        FabSecondaryTokens.ContainerColor.toColor()
+    val FloatingActionButtonContainerColor: Color
+        @Composable get() =
+            FabSecondaryTokens.ContainerColor.toColor()
 
     /** The shape of a [BottomAppBar]'s [FloatingActionButton] */
-    val FloatingActionButtonShape: Shape @Composable get() =
-        FabSecondaryTokens.ContainerShape.toShape()
+    val FloatingActionButtonShape: Shape
+        @Composable get() =
+            FabSecondaryTokens.ContainerShape.toShape()
 
     /**
      * The default [FloatingActionButton] for [BottomAppBar]
@@ -878,15 +921,15 @@
             FloatingActionButtonElevation,
         content: @Composable () -> Unit,
     ) = androidx.compose.material3.FloatingActionButton(
-            onClick = onClick,
-            modifier = modifier,
-            interactionSource = interactionSource,
-            shape = shape,
-            containerColor = containerColor,
-            contentColor = contentColor,
-            elevation = elevation,
-            content = content
-        )
+        onClick = onClick,
+        modifier = modifier,
+        interactionSource = interactionSource,
+        shape = shape,
+        containerColor = containerColor,
+        contentColor = contentColor,
+        elevation = elevation,
+        content = content
+    )
 }
 
 // Padding minus IconButton's min touch target expansion
@@ -907,6 +950,7 @@
  * [centeredTitle] flag is true, the title will be horizontally aligned to the center of the top app
  * bar width.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SingleRowTopAppBar(
     modifier: Modifier = Modifier,
@@ -918,20 +962,22 @@
     colors: TopAppBarColors,
     scrollBehavior: TopAppBarScrollBehavior?
 ) {
-    // TODO(b/182393826): Check if there is a better place to set the offsetLimit.
-    // Set a scroll offset limit to hide the entire app bar area when scrolling.
-    val offsetLimit = with(LocalDensity.current) { -TopAppBarSmallTokens.ContainerHeight.toPx() }
+    // Sets the app bar's height offset to collapse the entire bar's height when content is
+    // scrolled.
+    val heightOffsetLimit =
+        with(LocalDensity.current) { -TopAppBarSmallTokens.ContainerHeight.toPx() }
     SideEffect {
-        if (scrollBehavior?.state?.offsetLimit != offsetLimit) {
-            scrollBehavior?.state?.offsetLimit = offsetLimit
+        if (scrollBehavior?.state?.heightOffsetLimit != heightOffsetLimit) {
+            scrollBehavior?.state?.heightOffsetLimit = heightOffsetLimit
         }
     }
 
-    // Obtain the container color from the TopAppBarColors.
+    // Obtain the container color from the TopAppBarColors using the `overlapFraction`. This
+    // ensures that the colors will adjust whether the app bar behavior is pinned or scrolled.
     // This may potentially animate or interpolate a transition between the container-color and the
     // container's scrolled-color according to the app bar's scroll state.
-    val scrollFraction = scrollBehavior?.scrollFraction ?: 0f
-    val appBarContainerColor by colors.containerColor(scrollFraction)
+    val colorTransitionFraction = scrollBehavior?.state?.overlappedFraction ?: 0f
+    val appBarContainerColor by colors.containerColor(colorTransitionFraction)
 
     // Wrap the given actions in a Row.
     val actionsRow = @Composable {
@@ -941,25 +987,28 @@
             content = actions
         )
     }
-    // Compose a Surface with a TopAppBarLayout content. The surface's background color will be
-    // animated as specified above, and the height of the app bar will be determined by the current
-    // scroll-state offset.
+    // Compose a Surface with a TopAppBarLayout content.
+    // The surface's background color is animated as specified above.
+    // The height of the app bar is determined by subtracting the bar's height offset from the
+    // app bar's defined constant height value (i.e. the ContainerHeight token).
     Surface(modifier = modifier, color = appBarContainerColor) {
         val height = LocalDensity.current.run {
-            TopAppBarSmallTokens.ContainerHeight.toPx() + (scrollBehavior?.state?.offset ?: 0f)
+            TopAppBarSmallTokens.ContainerHeight.toPx() + (scrollBehavior?.state?.heightOffset
+                ?: 0f)
         }
         TopAppBarLayout(
             modifier = Modifier,
             heightPx = height,
-            navigationIconContentColor = colors.navigationIconContentColor(scrollFraction).value,
-            titleContentColor = colors.titleContentColor(scrollFraction).value,
-            actionIconContentColor = colors.actionIconContentColor(scrollFraction).value,
+            navigationIconContentColor =
+            colors.navigationIconContentColor(colorTransitionFraction).value,
+            titleContentColor = colors.titleContentColor(colorTransitionFraction).value,
+            actionIconContentColor = colors.actionIconContentColor(colorTransitionFraction).value,
             title = title,
             titleTextStyle = titleTextStyle,
             titleAlpha = 1f,
             titleVerticalArrangement = Arrangement.Center,
             titleHorizontalArrangement =
-                if (centeredTitle) Arrangement.Center else Arrangement.Start,
+            if (centeredTitle) Arrangement.Center else Arrangement.Start,
             titleBottomPadding = 0,
             hideTitleSemantics = false,
             navigationIcon = navigationIcon,
@@ -975,6 +1024,7 @@
  * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
  * [pinnedHeight]
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun TwoRowsTopAppBar(
     modifier: Modifier = Modifier,
@@ -1004,26 +1054,21 @@
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
     }
 
-    // Set a scroll offset limit that will hide just the title area and will keep the small title
-    // area visible.
+    // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
+    // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.offsetLimit != pinnedHeightPx - maxHeightPx) {
-            scrollBehavior?.state?.offsetLimit = pinnedHeightPx - maxHeightPx
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
         }
     }
 
-    val scrollPercentage =
-        if (scrollBehavior == null || scrollBehavior.state.offsetLimit == 0f) {
-            0f
-        } else {
-            scrollBehavior.state.offset / scrollBehavior.state.offsetLimit
-        }
-
-    // Obtain the container Color from the TopAppBarColors.
+    // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
+    // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
+    // collapse.
     // This will potentially animate or interpolate a transition between the container color and the
     // container's scrolled color according to the app bar's scroll state.
-    val scrollFraction = scrollBehavior?.scrollFraction ?: 0f
-    val appBarContainerColor by colors.containerColor(scrollFraction)
+    val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
+    val appBarContainerColor by colors.containerColor(colorTransitionFraction)
 
     // Wrap the given actions in a Row.
     val actionsRow = @Composable {
@@ -1033,10 +1078,10 @@
             content = actions
         )
     }
-    val titleAlpha = 1f - scrollPercentage
+    val titleAlpha = 1f - colorTransitionFraction
     // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
     // Hide the bottom row title semantics when the top title semantics are active.
-    val hideTopRowSemantics = scrollPercentage < 0.5f
+    val hideTopRowSemantics = colorTransitionFraction < 0.5f
     val hideBottomRowSemantics = !hideTopRowSemantics
     Surface(modifier = modifier, color = appBarContainerColor) {
         Column {
@@ -1044,9 +1089,10 @@
                 modifier = Modifier,
                 heightPx = pinnedHeightPx,
                 navigationIconContentColor =
-                    colors.navigationIconContentColor(scrollFraction).value,
-                titleContentColor = colors.titleContentColor(scrollFraction).value,
-                actionIconContentColor = colors.actionIconContentColor(scrollFraction).value,
+                colors.navigationIconContentColor(colorTransitionFraction).value,
+                titleContentColor = colors.titleContentColor(colorTransitionFraction).value,
+                actionIconContentColor =
+                colors.actionIconContentColor(colorTransitionFraction).value,
                 title = smallTitle,
                 titleTextStyle = smallTitleTextStyle,
                 titleAlpha = 1f - titleAlpha,
@@ -1059,11 +1105,13 @@
             )
             TopAppBarLayout(
                 modifier = Modifier.clipToBounds(),
-                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.offset ?: 0f),
+                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
+                    ?: 0f),
                 navigationIconContentColor =
-                colors.navigationIconContentColor(scrollFraction).value,
-                titleContentColor = colors.titleContentColor(scrollFraction).value,
-                actionIconContentColor = colors.actionIconContentColor(scrollFraction).value,
+                colors.navigationIconContentColor(colorTransitionFraction).value,
+                titleContentColor = colors.titleContentColor(colorTransitionFraction).value,
+                actionIconContentColor =
+                colors.actionIconContentColor(colorTransitionFraction).value,
                 title = title,
                 titleTextStyle = titleTextStyle,
                 titleAlpha = titleAlpha,
@@ -1126,7 +1174,8 @@
             Box(
                 Modifier
                     .layoutId("navigationIcon")
-                    .padding(start = TopAppBarHorizontalPadding)) {
+                    .padding(start = TopAppBarHorizontalPadding)
+            ) {
                 CompositionLocalProvider(
                     LocalContentColor provides navigationIconContentColor,
                     content = navigationIcon
@@ -1148,7 +1197,8 @@
             Box(
                 Modifier
                     .layoutId("actionIcons")
-                    .padding(end = TopAppBarHorizontalPadding)) {
+                    .padding(end = TopAppBarHorizontalPadding)
+            ) {
                 CompositionLocalProvider(
                     LocalContentColor provides actionIconContentColor,
                     content = actions
@@ -1242,16 +1292,16 @@
 ) : TopAppBarColors {
 
     // In this TopAppBarColors implementation, the following colors never change their value as the
-    // app bar scrolls.
+    // app bar collapses.
     private val navigationIconColorState: State = mutableStateOf(navigationIconContentColor)
     private val titleColorState: State = mutableStateOf(titleContentColor)
     private val actionIconColorState: State = mutableStateOf(actionIconContentColor)
 
     @Composable
-    override fun containerColor(scrollFraction: Float): State {
+    override fun containerColor(colorTransitionFraction: Float): State {
         return animateColorAsState(
-            // Check if scrollFraction is slightly over zero to overcome float precision issues.
-            targetValue = if (scrollFraction > 0.01f) {
+            // Check if fraction is slightly over zero to overcome float precision issues.
+            targetValue = if (colorTransitionFraction > 0.01f) {
                 scrolledContainerColor
             } else {
                 containerColor
@@ -1264,14 +1314,15 @@
     }
 
     @Composable
-    override fun navigationIconContentColor(scrollFraction: Float): State =
+    override fun navigationIconContentColor(colorTransitionFraction: Float): State =
         navigationIconColorState
 
     @Composable
-    override fun titleContentColor(scrollFraction: Float): State = titleColorState
+    override fun titleContentColor(colorTransitionFraction: Float): State = titleColorState
 
     @Composable
-    override fun actionIconContentColor(scrollFraction: Float): State = actionIconColorState
+    override fun actionIconContentColor(colorTransitionFraction: Float): State =
+        actionIconColorState
 }
 
 /**
@@ -1290,31 +1341,32 @@
 ) : TopAppBarColors {
 
     // In this TopAppBarColors implementation, the following colors never change their value as the
-    // app bar scrolls.
+    // app bar collapses.
     private val navigationIconColorState: State = mutableStateOf(navigationIconContentColor)
     private val titleColorState: State = mutableStateOf(titleContentColor)
     private val actionIconColorState: State = mutableStateOf(actionIconContentColor)
 
     @Composable
-    override fun containerColor(scrollFraction: Float): State {
+    override fun containerColor(colorTransitionFraction: Float): State {
         return rememberUpdatedState(
             lerp(
                 containerColor,
                 scrolledContainerColor,
-                FastOutLinearInEasing.transform(scrollFraction)
+                FastOutLinearInEasing.transform(colorTransitionFraction)
             )
         )
     }
 
     @Composable
-    override fun navigationIconContentColor(scrollFraction: Float): State =
+    override fun navigationIconContentColor(colorTransitionFraction: Float): State =
         navigationIconColorState
 
     @Composable
-    override fun titleContentColor(scrollFraction: Float): State = titleColorState
+    override fun titleContentColor(colorTransitionFraction: Float): State = titleColorState
 
     @Composable
-    override fun actionIconContentColor(scrollFraction: Float): State = actionIconColorState
+    override fun actionIconContentColor(colorTransitionFraction: Float): State =
+        actionIconColorState
 }
 
 /**
@@ -1324,19 +1376,11 @@
  * @param canScroll a callback used to determine whether scroll events are to be
  * handled by this [PinnedScrollBehavior]
  */
+@OptIn(ExperimentalMaterial3Api::class)
 private class PinnedScrollBehavior(
-    override var state: TopAppBarScrollState,
+    override var state: TopAppBarState,
     val canScroll: () -> Boolean = { true }
 ) : TopAppBarScrollBehavior {
-    override val scrollFraction: Float
-        get() = if (state.offsetLimit != 0f) {
-            1 - ((state.offsetLimit - state.contentOffset).coerceIn(
-                minimumValue = state.offsetLimit,
-                maximumValue = 0f
-            ) / state.offsetLimit)
-        } else {
-            0f
-        }
     override var nestedScrollConnection =
         object : NestedScrollConnection {
             override fun onPostScroll(
@@ -1346,8 +1390,8 @@
             ): Offset {
                 if (!canScroll()) return Offset.Zero
                 if (consumed.y == 0f && available.y > 0f) {
-                    // Reset the total offset to zero when scrolling all the way down. This will
-                    // eliminate some float precision inaccuracies.
+                    // Reset the total content offset to zero when scrolling all the way down.
+                    // This will eliminate some float precision inaccuracies.
                     state.contentOffset = 0f
                 } else {
                     state.contentOffset += consumed.y
@@ -1367,30 +1411,22 @@
  * @param canScroll a callback used to determine whether scroll events are to be
  * handled by this [EnterAlwaysScrollBehavior]
  */
+@OptIn(ExperimentalMaterial3Api::class)
 private class EnterAlwaysScrollBehavior(
-    override var state: TopAppBarScrollState,
+    override var state: TopAppBarState,
     val canScroll: () -> Boolean = { true }
 ) : TopAppBarScrollBehavior {
-    override val scrollFraction: Float
-        get() = if (state.offsetLimit != 0f) {
-            1 - ((state.offsetLimit - state.contentOffset).coerceIn(
-                minimumValue = state.offsetLimit,
-                maximumValue = 0f
-            ) / state.offsetLimit)
-        } else {
-            0f
-        }
     override var nestedScrollConnection =
         object : NestedScrollConnection {
             override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                 if (!canScroll()) return Offset.Zero
-                val newOffset = (state.offset + available.y)
+                val newOffset = (state.heightOffset + available.y)
                 val coerced =
-                    newOffset.coerceIn(minimumValue = state.offsetLimit, maximumValue = 0f)
+                    newOffset.coerceIn(minimumValue = state.heightOffsetLimit, maximumValue = 0f)
                 return if (newOffset == coerced) {
                     // Nothing coerced, meaning we're in the middle of top app bar collapse or
                     // expand.
-                    state.offset = coerced
+                    state.heightOffset = coerced
                     // Consume only the scroll on the Y axis.
                     available.copy(x = 0f)
                 } else {
@@ -1405,15 +1441,15 @@
             ): Offset {
                 if (!canScroll()) return Offset.Zero
                 state.contentOffset += consumed.y
-                if (state.offset == 0f || state.offset == state.offsetLimit) {
+                if (state.heightOffset == 0f || state.heightOffset == state.heightOffsetLimit) {
                     if (consumed.y == 0f && available.y > 0f) {
-                        // Reset the total offset to zero when scrolling all the way down.
+                        // Reset the total content offset to zero when scrolling all the way down.
                         // This will eliminate some float precision inaccuracies.
                         state.contentOffset = 0f
                     }
                 }
-                state.offset = (state.offset + consumed.y).coerceIn(
-                    minimumValue = state.offsetLimit,
+                state.heightOffset = (state.heightOffset + consumed.y).coerceIn(
+                    minimumValue = state.heightOffsetLimit,
                     maximumValue = 0f
                 )
                 return Offset.Zero
@@ -1436,26 +1472,25 @@
  * @param canScroll a callback used to determine whether scroll events are to be
  * handled by this [ExitUntilCollapsedScrollBehavior]
  */
+@OptIn(ExperimentalMaterial3Api::class)
 private class ExitUntilCollapsedScrollBehavior(
-    override val state: TopAppBarScrollState,
+    override val state: TopAppBarState,
     val decayAnimationSpec: DecayAnimationSpec,
     val canScroll: () -> Boolean = { true }
 ) : TopAppBarScrollBehavior {
-    override val scrollFraction: Float
-        get() = if (state.offsetLimit != 0f) state.offset / state.offsetLimit else 0f
     override var nestedScrollConnection =
         object : NestedScrollConnection {
             override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                 // Don't intercept if scrolling down.
                 if (!canScroll() || available.y > 0f) return Offset.Zero
 
-                val newOffset = (state.offset + available.y)
+                val newOffset = (state.heightOffset + available.y)
                 val coerced =
-                    newOffset.coerceIn(minimumValue = state.offsetLimit, maximumValue = 0f)
+                    newOffset.coerceIn(minimumValue = state.heightOffsetLimit, maximumValue = 0f)
                 return if (newOffset == coerced) {
                     // Nothing coerced, meaning we're in the middle of top app bar collapse or
                     // expand.
-                    state.offset = coerced
+                    state.heightOffset = coerced
                     // Consume only the scroll on the Y axis.
                     available.copy(x = 0f)
                 } else {
@@ -1472,30 +1507,30 @@
                 state.contentOffset += consumed.y
 
                 if (available.y < 0f || consumed.y < 0f) {
-                    // When scrolling up, just update the state's offset.
-                    val oldOffset = state.offset
-                    state.offset = (state.offset + consumed.y).coerceIn(
-                        minimumValue = state.offsetLimit,
+                    // When scrolling up, just update the state's height offset.
+                    val oldHeightOffset = state.heightOffset
+                    state.heightOffset = (state.heightOffset + consumed.y).coerceIn(
+                        minimumValue = state.heightOffsetLimit,
                         maximumValue = 0f
                     )
-                    return Offset(0f, state.offset - oldOffset)
+                    return Offset(0f, state.heightOffset - oldHeightOffset)
                 }
 
                 if (consumed.y == 0f && available.y > 0) {
-                    // Reset the total offset to zero when scrolling all the way down. This will
-                    // eliminate some float precision inaccuracies.
+                    // Reset the total content offset to zero when scrolling all the way down. This
+                    // will eliminate some float precision inaccuracies.
                     state.contentOffset = 0f
                 }
 
                 if (available.y > 0f) {
-                    // Adjust the offset in case the consumed delta Y is less than what was recorded
-                    // as available delta Y in the pre-scroll.
-                    val oldOffset = state.offset
-                    state.offset = (state.offset + available.y).coerceIn(
-                        minimumValue = state.offsetLimit,
+                    // Adjust the height offset in case the consumed delta Y is less than what was
+                    // recorded as available delta Y in the pre-scroll.
+                    val oldHeightOffset = state.heightOffset
+                    state.heightOffset = (state.heightOffset + available.y).coerceIn(
+                        minimumValue = state.heightOffsetLimit,
                         maximumValue = 0f
                     )
-                    return Offset(0f, state.offset - oldOffset)
+                    return Offset(0f, state.heightOffset - oldHeightOffset)
                 }
                 return Offset.Zero
             }
@@ -1505,7 +1540,7 @@
                 // TODO(b/179417109): We get positive Velocity when flinging up while the top app
                 //  bar is changing its height. Track b/179417109 for a fix.
                 if ((available.y < 0f && state.contentOffset == 0f) ||
-                    (available.y > 0f && state.offset < 0f)
+                    (available.y > 0f && state.heightOffset < 0f)
                 ) {
                     return result +
                         onTopBarFling(
@@ -1520,6 +1555,7 @@
         }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 private suspend fun onTopBarFling(
     scrollBehavior: TopAppBarScrollBehavior,
     initialVelocity: Float,
@@ -1535,13 +1571,13 @@
         )
             .animateDecay(decayAnimationSpec) {
                 val delta = value - lastValue
-                val initialOffset = scrollBehavior.state.offset
-                scrollBehavior.state.offset =
-                    (initialOffset + delta).coerceIn(
-                        minimumValue = scrollBehavior.state.offsetLimit,
+                val initialHeightOffset = scrollBehavior.state.heightOffset
+                scrollBehavior.state.heightOffset =
+                    (initialHeightOffset + delta).coerceIn(
+                        minimumValue = scrollBehavior.state.heightOffsetLimit,
                         maximumValue = 0f
                     )
-                val consumed = abs(initialOffset - scrollBehavior.state.offset)
+                val consumed = abs(initialHeightOffset - scrollBehavior.state.heightOffset)
                 lastValue = value
                 remainingVelocity = this.velocity
                 // avoid rounding errors and stop if anything is unconsumed
@@ -1549,18 +1585,18 @@
             }
 
         if (snap &&
-            scrollBehavior.state.offset < 0 &&
-            scrollBehavior.state.offset > scrollBehavior.state.offsetLimit
+            scrollBehavior.state.heightOffset < 0 &&
+            scrollBehavior.state.heightOffset > scrollBehavior.state.heightOffsetLimit
         ) {
-            AnimationState(initialValue = scrollBehavior.state.offset).animateTo(
-                // Snap the top app bar offset to completely collapse or completely expand according
-                // to the initial velocity direction.
-                if (initialVelocity > 0) 0f else scrollBehavior.state.offsetLimit,
+            AnimationState(initialValue = scrollBehavior.state.heightOffset).animateTo(
+                // Snap the top app bar height offset to have the bar completely collapse or
+                // completely expand according to the initial velocity direction.
+                if (initialVelocity > 0) 0f else scrollBehavior.state.heightOffsetLimit,
                 animationSpec = tween(
                     durationMillis = TopAppBarAnimationDurationMillis,
                     easing = LinearOutSlowInEasing
                 )
-            ) { scrollBehavior.state.offset = value }
+            ) { scrollBehavior.state.heightOffset = value }
         }
         return Velocity(0f, remainingVelocity)
     }
@@ -1571,9 +1607,6 @@
 private val LargeTitleBottomPadding = 28.dp
 private val TopAppBarHorizontalPadding = 4.dp
 
-// TODO: this should probably be part of the touch target of the start and end icons, clarify this
-private val AppBarHorizontalPadding = 4.dp
-
 // A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the
 // navigation icon is missing.
 private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
index 693ac03..071b52b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
@@ -358,6 +358,7 @@
 /**
  * Contains the default values used by list items.
  */
+@ExperimentalMaterial3Api
 object ListItemDefaults {
     /** The default shape of a list item */
     val Shape: Shape @Composable get() = ListTokens.ListItemContainerShape.toShape()
@@ -422,6 +423,7 @@
  * - See [ListItemDefaults.colors] for the default colors used in a [ListItem].
  */
 @Stable
+@ExperimentalMaterial3Api
 interface ListItemColors {
 
     /** The container color of this [ListItem] based on enabled state */
@@ -450,6 +452,7 @@
 }
 
 /** Default [ListItemColors] implementation. */
+@ExperimentalMaterial3Api
 @Immutable
 private class DefaultListItemColors(
     private val containerColor: Color,
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index b93c91c..986266b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotContextElement
+
 // TODO(aelias): Mark the typealiases internal when https://youtrack.jetbrains.com/issue/KT-36695 is fixed.
 // Currently, they behave as internal because the actual is internal, even though the expect is public.
 
@@ -117,3 +120,8 @@
     composer: Composer,
     composable: @Composable () -> T
 ): T
+
+@OptIn(ExperimentalComposeApi::class)
+internal expect class SnapshotContextElementImpl(
+    snapshot: Snapshot
+) : SnapshotContextElement
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElementTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElementTests.kt
index 4efd10c..762d20c 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElementTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElementTests.kt
@@ -21,13 +21,15 @@
 import kotlin.test.assertSame
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@OptIn(ExperimentalComposeApi::class)
+@OptIn(ExperimentalComposeApi::class, ExperimentalCoroutinesApi::class)
 class SnapshotContextElementTests {
     @Test
-    fun coroutineEntersExpectedSnapshot() = runBlocking {
+    fun coroutineEntersExpectedSnapshot() = runTest(UnconfinedTestDispatcher()) {
         val snapshot = Snapshot.takeSnapshot()
         try {
             withContext(snapshot.asContextElement()) {
@@ -43,7 +45,7 @@
         val snapshotOne = Snapshot.takeSnapshot()
         val snapshotTwo = Snapshot.takeSnapshot()
         try {
-            runBlocking {
+            runTest(UnconfinedTestDispatcher()) {
                 val stopA = Job()
                 val jobA = launch(snapshotOne.asContextElement()) {
                     assertSame(snapshotOne, Snapshot.current, "expected snapshotOne, A")
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
index 31f8ed3..14ce111 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
@@ -18,6 +18,10 @@
 
 import androidx.compose.runtime.internal.ThreadMap
 import androidx.compose.runtime.internal.emptyThreadMap
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotContextElement
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ThreadContextElement
 
 internal actual typealias AtomicReference = java.util.concurrent.atomic.AtomicReference
 
@@ -91,3 +95,22 @@
 }
 
 internal actual fun ensureMutable(it: Any) { /* NOTHING */ }
+
+/**
+ * Implementation of [SnapshotContextElement] that enters a single given snapshot when updating
+ * the thread context of a resumed coroutine.
+ */
+@ExperimentalComposeApi
+internal actual class SnapshotContextElementImpl actual constructor(
+    private val snapshot: Snapshot
+) : SnapshotContextElement, ThreadContextElement {
+    override val key: CoroutineContext.Key<*>
+        get() = SnapshotContextElement
+
+    override fun updateThreadContext(context: CoroutineContext): Snapshot? =
+        snapshot.unsafeEnter()
+
+    override fun restoreThreadContext(context: CoroutineContext, oldState: Snapshot?) {
+        snapshot.unsafeLeave(oldState)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-geometry/api/current.txt b/compose/ui/ui-geometry/api/current.txt
index 936778a..583e373 100644
--- a/compose/ui/ui-geometry/api/current.txt
+++ b/compose/ui/ui-geometry/api/current.txt
@@ -32,7 +32,7 @@
 
   public final class MutableRect {
     ctor public MutableRect(float left, float top, float right, float bottom);
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public float getBottom();
     method public inline float getHeight();
     method public float getLeft();
@@ -105,7 +105,7 @@
     method public float component2();
     method public float component3();
     method public float component4();
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public androidx.compose.ui.geometry.Rect copy(float left, float top, float right, float bottom);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect deflate(float delta);
     method public float getBottom();
@@ -180,7 +180,7 @@
     method public long component6-kKHJgLs();
     method public long component7-kKHJgLs();
     method public long component8-kKHJgLs();
-    method public boolean contains(long point);
+    method public operator boolean contains(long point);
     method public androidx.compose.ui.geometry.RoundRect copy-MDFrsts(float left, float top, float right, float bottom, long topLeftCornerRadius, long topRightCornerRadius, long bottomRightCornerRadius, long bottomLeftCornerRadius);
     method public float getBottom();
     method public long getBottomLeftCornerRadius();
diff --git a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
index 936778a..583e373 100644
--- a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
@@ -32,7 +32,7 @@
 
   public final class MutableRect {
     ctor public MutableRect(float left, float top, float right, float bottom);
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public float getBottom();
     method public inline float getHeight();
     method public float getLeft();
@@ -105,7 +105,7 @@
     method public float component2();
     method public float component3();
     method public float component4();
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public androidx.compose.ui.geometry.Rect copy(float left, float top, float right, float bottom);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect deflate(float delta);
     method public float getBottom();
@@ -180,7 +180,7 @@
     method public long component6-kKHJgLs();
     method public long component7-kKHJgLs();
     method public long component8-kKHJgLs();
-    method public boolean contains(long point);
+    method public operator boolean contains(long point);
     method public androidx.compose.ui.geometry.RoundRect copy-MDFrsts(float left, float top, float right, float bottom, long topLeftCornerRadius, long topRightCornerRadius, long bottomRightCornerRadius, long bottomLeftCornerRadius);
     method public float getBottom();
     method public long getBottomLeftCornerRadius();
diff --git a/compose/ui/ui-geometry/api/restricted_current.txt b/compose/ui/ui-geometry/api/restricted_current.txt
index 936778a..583e373 100644
--- a/compose/ui/ui-geometry/api/restricted_current.txt
+++ b/compose/ui/ui-geometry/api/restricted_current.txt
@@ -32,7 +32,7 @@
 
   public final class MutableRect {
     ctor public MutableRect(float left, float top, float right, float bottom);
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public float getBottom();
     method public inline float getHeight();
     method public float getLeft();
@@ -105,7 +105,7 @@
     method public float component2();
     method public float component3();
     method public float component4();
-    method public boolean contains(long offset);
+    method public operator boolean contains(long offset);
     method public androidx.compose.ui.geometry.Rect copy(float left, float top, float right, float bottom);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect deflate(float delta);
     method public float getBottom();
@@ -180,7 +180,7 @@
     method public long component6-kKHJgLs();
     method public long component7-kKHJgLs();
     method public long component8-kKHJgLs();
-    method public boolean contains(long point);
+    method public operator boolean contains(long point);
     method public androidx.compose.ui.geometry.RoundRect copy-MDFrsts(float left, float top, float right, float bottom, long topLeftCornerRadius, long topRightCornerRadius, long bottomRightCornerRadius, long bottomLeftCornerRadius);
     method public float getBottom();
     method public long getBottomLeftCornerRadius();
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt
index b00c587..1e2d5b1 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt
@@ -77,7 +77,7 @@
      * Rectangles include their top and left edges but exclude their bottom and
      * right edges.
      */
-    fun contains(offset: Offset): Boolean {
+    operator fun contains(offset: Offset): Boolean {
         return offset.x >= left && offset.x < right && offset.y >= top && offset.y < bottom
     }
 
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
index 6c976ed..5230b42 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
@@ -244,7 +244,7 @@
      * Rectangles include their top and left edges but exclude their bottom and
      * right edges.
      */
-    fun contains(offset: Offset): Boolean {
+    operator fun contains(offset: Offset): Boolean {
         return offset.x >= left && offset.x < right && offset.y >= top && offset.y < bottom
     }
 
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/RoundRect.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/RoundRect.kt
index 9e1484c..2ce06a3 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/RoundRect.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/RoundRect.kt
@@ -122,7 +122,7 @@
      * using this method, prefer to reuse existing [RoundRect]s rather than
      * recreating the object each time.
      */
-    fun contains(point: Offset): Boolean {
+    operator fun contains(point: Offset): Boolean {
         if (point.x < left || point.x >= right || point.y < top || point.y >= bottom) {
             return false; // outside bounding box
         }
diff --git a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt
index 1750492..ce9a9c9 100644
--- a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt
+++ b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt
@@ -51,18 +51,18 @@
     @Test
     fun contains() {
         val r = MutableRect(1f, 3f, 5f, 9f)
-        assertTrue(r.contains(Offset(1f, 3f)))
-        assertTrue(r.contains(Offset(3f, 3f)))
-        assertFalse(r.contains(Offset(5f, 3f)))
-        assertTrue(r.contains(Offset(1f, 6f)))
-        assertTrue(r.contains(Offset(3f, 6f)))
-        assertFalse(r.contains(Offset(5f, 6f)))
-        assertFalse(r.contains(Offset(1f, 9f)))
-        assertFalse(r.contains(Offset(3f, 9f)))
-        assertFalse(r.contains(Offset(5f, 9f)))
-        assertFalse(r.contains(Offset(0f, 0f)))
-        assertFalse(r.contains(Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)))
-        assertFalse(r.contains(Offset(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)))
+        assertTrue(Offset(1f, 3f) in r)
+        assertTrue(Offset(3f, 3f) in r)
+        assertFalse(Offset(5f, 3f) in r)
+        assertTrue(Offset(1f, 6f) in r)
+        assertTrue(Offset(3f, 6f) in r)
+        assertFalse(Offset(5f, 6f) in r)
+        assertFalse(Offset(1f, 9f) in r)
+        assertFalse(Offset(3f, 9f) in r)
+        assertFalse(Offset(5f, 9f) in r)
+        assertFalse(Offset(0f, 0f) in r)
+        assertFalse(Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY) in r)
+        assertFalse(Offset(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY) in r)
     }
 
     @Test
diff --git a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
index b2e5432..927b408 100644
--- a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
+++ b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
@@ -223,17 +223,17 @@
     fun `rect contains`() {
         val rect = Rect(100f, 10f, 200f, 300f)
         val offset = Offset(177f, 288f)
-        assertTrue(rect.contains(offset))
+        assertTrue(offset in rect)
     }
 
     @Test
     fun `rect does not contain`() {
         val rect = Rect(100f, 10f, 200f, 300f)
         val offset1 = Offset(201f, 150f)
-        assertFalse(rect.contains(offset1))
+        assertFalse(offset1 in rect)
 
         val offset2 = Offset(200f, 301f)
-        assertFalse(rect.contains(offset2))
+        assertFalse(offset2 in rect)
     }
 
     @Test
diff --git a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RoundRectTest.kt b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RoundRectTest.kt
index 0880e8e..1f6aeac 100644
--- a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RoundRectTest.kt
+++ b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RoundRectTest.kt
@@ -36,14 +36,14 @@
             bottomLeft = CornerRadius.Zero
         )
 
-        assertFalse(roundRect.contains(Offset(1.0f, 1.0f)))
-        assertFalse(roundRect.contains(Offset(1.1f, 1.1f)))
-        assertTrue(roundRect.contains(Offset(1.15f, 1.15f)))
-        assertFalse(roundRect.contains(Offset(2.0f, 1.0f)))
-        assertFalse(roundRect.contains(Offset(1.93f, 1.07f)))
-        assertFalse(roundRect.contains(Offset(1.97f, 1.7f)))
-        assertTrue(roundRect.contains(Offset(1.7f, 1.97f)))
-        assertTrue(roundRect.contains(Offset(1.0f, 1.99f)))
+        assertFalse(Offset(1.0f, 1.0f) in roundRect)
+        assertFalse(Offset(1.1f, 1.1f) in roundRect)
+        assertTrue(Offset(1.15f, 1.15f) in roundRect)
+        assertFalse(Offset(2.0f, 1.0f) in roundRect)
+        assertFalse(Offset(1.93f, 1.07f) in roundRect)
+        assertFalse(Offset(1.97f, 1.7f) in roundRect)
+        assertTrue(Offset(1.7f, 1.97f) in roundRect)
+        assertTrue(Offset(1.0f, 1.99f) in roundRect)
     }
 
     @Test
@@ -56,14 +56,14 @@
             bottomLeft = CornerRadius.Zero
         )
 
-        assertFalse(roundRect.contains(Offset(1.0f, 1.0f)))
-        assertFalse(roundRect.contains(Offset(1.1f, 1.1f)))
-        assertTrue(roundRect.contains(Offset(1.15f, 1.15f)))
-        assertFalse(roundRect.contains(Offset(2.0f, 1.0f)))
-        assertFalse(roundRect.contains(Offset(1.93f, 1.07f)))
-        assertFalse(roundRect.contains(Offset(1.97f, 1.7f)))
-        assertTrue(roundRect.contains(Offset(1.7f, 1.97f)))
-        assertTrue(roundRect.contains(Offset(1.0f, 1.99f)))
+        assertFalse(Offset(1.0f, 1.0f) in roundRect)
+        assertFalse(Offset(1.1f, 1.1f) in roundRect)
+        assertTrue(Offset(1.15f, 1.15f) in roundRect)
+        assertFalse(Offset(2.0f, 1.0f) in roundRect)
+        assertFalse(Offset(1.93f, 1.07f) in roundRect)
+        assertFalse(Offset(1.97f, 1.7f) in roundRect)
+        assertTrue(Offset(1.7f, 1.97f) in roundRect)
+        assertTrue(Offset(1.0f, 1.99f) in roundRect)
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index 8ea7c85..5adc1c1 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -220,6 +220,7 @@
         assertTrue(info.isHeading)
         assertTrue(info.isClickable)
         assertTrue(info.isVisibleToUser)
+        assertTrue(info.isImportantForAccessibility)
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 4775e3a..ad153c9 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -431,6 +431,12 @@
 
         info.packageName = view.context.packageName
 
+        // This property exists to distinguish semantically meaningful nodes from purely structural
+        // or decorative UI elements. In Compose, LayoutNodes without semantics are simply omitted
+        // from the AccessibilityNodeInfo tree. Therefore, every AccessibilityNodeInfo qualifies as
+        // "important".
+        info.isImportantForAccessibility = true
+
         semanticsNode.replacedChildrenSortedByBounds.fastForEach { child ->
             if (currentSemanticsNodes.contains(child.id)) {
                 val holder = view.androidViewsHandler.layoutNodeToHolder[child.layoutNode]
diff --git a/core/core-ktx/api/current.txt b/core/core-ktx/api/current.txt
index 521f26c..6434f94 100644
--- a/core/core-ktx/api/current.txt
+++ b/core/core-ktx/api/current.txt
@@ -563,6 +563,7 @@
     method public static inline boolean isNotEmpty(android.view.Menu);
     method public static operator java.util.Iterator iterator(android.view.Menu);
     method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+    method public static inline void removeItemAt(android.view.Menu, int index);
   }
 
   public final class ViewGroupKt {
diff --git a/core/core-ktx/api/public_plus_experimental_current.txt b/core/core-ktx/api/public_plus_experimental_current.txt
index 521f26c..6434f94 100644
--- a/core/core-ktx/api/public_plus_experimental_current.txt
+++ b/core/core-ktx/api/public_plus_experimental_current.txt
@@ -563,6 +563,7 @@
     method public static inline boolean isNotEmpty(android.view.Menu);
     method public static operator java.util.Iterator iterator(android.view.Menu);
     method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+    method public static inline void removeItemAt(android.view.Menu, int index);
   }
 
   public final class ViewGroupKt {
diff --git a/core/core-ktx/api/restricted_current.txt b/core/core-ktx/api/restricted_current.txt
index 521f26c..6434f94 100644
--- a/core/core-ktx/api/restricted_current.txt
+++ b/core/core-ktx/api/restricted_current.txt
@@ -563,6 +563,7 @@
     method public static inline boolean isNotEmpty(android.view.Menu);
     method public static operator java.util.Iterator iterator(android.view.Menu);
     method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+    method public static inline void removeItemAt(android.view.Menu, int index);
   }
 
   public final class ViewGroupKt {
diff --git a/core/core-ktx/src/androidTest/java/androidx/core/view/MenuTest.kt b/core/core-ktx/src/androidTest/java/androidx/core/view/MenuTest.kt
index 6e195ce..9d5482a 100644
--- a/core/core-ktx/src/androidTest/java/androidx/core/view/MenuTest.kt
+++ b/core/core-ktx/src/androidTest/java/androidx/core/view/MenuTest.kt
@@ -136,8 +136,8 @@
     }
 
     @Test fun iteratorRemoving() {
-        val item1 = menu.add("")
-        val item2 = menu.add("")
+        val item1 = menu.add(NONE, 9, NONE, "")
+        val item2 = menu.add(NONE, 13, NONE, "")
 
         val iterator = menu.iterator()
 
@@ -163,4 +163,17 @@
             assertSame(items[index], child)
         }
     }
+
+    @Test fun removeItemAt() {
+        val item1 = menu.add(NONE, 9, NONE, "")
+        val item2 = menu.add(NONE, 13, NONE, "")
+
+        menu.removeItemAt(0)
+        assertFalse(item1 in menu)
+        assertEquals(1, menu.size())
+
+        menu.removeItemAt(0)
+        assertFalse(item2 in menu)
+        assertEquals(0, menu.size())
+    }
 }
diff --git a/core/core-ktx/src/main/java/androidx/core/view/Menu.kt b/core/core-ktx/src/main/java/androidx/core/view/Menu.kt
index 804c64b..91095ce 100644
--- a/core/core-ktx/src/main/java/androidx/core/view/Menu.kt
+++ b/core/core-ktx/src/main/java/androidx/core/view/Menu.kt
@@ -71,9 +71,17 @@
         private var index = 0
         override fun hasNext() = index < size()
         override fun next() = getItem(index++) ?: throw IndexOutOfBoundsException()
-        override fun remove() = removeItem(--index)
+        override fun remove() = removeItemAt(--index)
     }
 
+/**
+ * Removes the menu item at the specified index.
+ *
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the count.
+ */
+public inline fun Menu.removeItemAt(index: Int) =
+    getItem(index)?.let { removeItem(it.itemId) } ?: throw IndexOutOfBoundsException()
+
 /** Returns a [Sequence] over the items in this menu. */
 public val Menu.children: Sequence
     get() = object : Sequence {
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index 350d71c..64623b1 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -589,7 +589,7 @@
 
     @FlakyTest(bugId = 206644987)
     @Test
-    @SdkSuppress(minSdkVersion = 19)
+    @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 32) // API 33 fails 100% b/233396250
     public void testSetAccessibilityPaneTitle_sendsOutCorrectEvent() throws TimeoutException {
         final Activity activity = mActivityTestRule.getActivity();
         final CharSequence title = "Sample title";
diff --git a/datastore/datastore-core-okio/build.gradle b/datastore/datastore-core-okio/build.gradle
index 1e6cc67..11f42b4 100644
--- a/datastore/datastore-core-okio/build.gradle
+++ b/datastore/datastore-core-okio/build.gradle
@@ -58,6 +58,8 @@
                 api(libs.kotlinTestCommon)
                 api(libs.kotlinTestAnnotationsCommon)
                 api(libs.kotlinCoroutinesTest)
+                api(project(":internal-testutils-datastore"))
+                api(project(":internal-testutils-kmp"))
             }
         }
         jvmTest {
@@ -65,8 +67,11 @@
                 implementation(libs.kotlinCoroutinesTest)
                 implementation(libs.kotlinTest)
                 implementation(libs.kotlinTestAnnotationsCommon)
+                api(project(":internal-testutils-datastore"))
+                api(project(":internal-testutils-kmp"))
             }
         }
+
         if (enableNative) {
             nativeMain {
                 dependencies {
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
index 3a326cd..7ae42ea 100644
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
+++ b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core.okio
 
+import androidx.datastore.OkioTestIO
 import androidx.datastore.core.ReadScope
 import androidx.datastore.core.Storage
 import androidx.datastore.core.StorageConnection
@@ -23,6 +24,8 @@
 import androidx.datastore.core.readData
 import androidx.datastore.core.use
 import androidx.datastore.core.writeData
+import androidx.datastore.TestingOkioSerializer
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,21 +46,21 @@
 class OkioStorageTest {
 
     private lateinit var testPath: Path
-    private lateinit var testingSerializer: TestingSerializer
+    private lateinit var testingSerializerConfig: TestingSerializerConfig
+    private lateinit var testingSerializer: TestingOkioSerializer
     private lateinit var testStorage: Storage
     private lateinit var testConnection: StorageConnection
     private lateinit var testScope: TestScope
-    private lateinit var fileScope: TestScope
-    private lateinit var testIO: TestIO
+    private lateinit var testIO: OkioTestIO
     private var fileSystem: FileSystem = FileSystem.SYSTEM
 
     @BeforeTest
     fun setUp() {
-        testIO = TestIO()
-        testingSerializer = TestingSerializer()
-        testPath = testIO.tempDir() / "temp-file.tmpo"
+        testIO = OkioTestIO()
+        testingSerializerConfig = TestingSerializerConfig()
+        testingSerializer = TestingOkioSerializer(testingSerializerConfig)
+        testPath = testIO.newTempFile().path
         testScope = TestScope(UnconfinedTestDispatcher())
-        fileScope = TestScope(UnconfinedTestDispatcher())
         testStorage = OkioStorage(fileSystem, testingSerializer) { testPath }
         testConnection = testStorage.createConnection()
     }
@@ -67,7 +70,7 @@
 
         val data = testConnection.readData()
 
-        assertThat(data).isEqualTo(0)
+        assertThat(data).isEqualTo(0.toByte())
     }
 
     @Test
@@ -179,7 +182,7 @@
 
         coroutineScope {
             testConnection.writeData(1)
-            testingSerializer.failingWrite = true
+            testingSerializerConfig.failingWrite = true
             assertThrows { testConnection.writeData(1) }
         }
 
@@ -275,7 +278,8 @@
 
     @Test
     fun testWriteToNonExistentDir() = testScope.runTest {
-        val fileInNonExistentDir = testIO.tempDir() / "this/does/not/exist/foo.tst"
+        val fileInNonExistentDir = FileSystem.SYSTEM_TEMPORARY_DIRECTORY /
+            "this/does/not/exist/foo.tst"
         coroutineScope {
             val storage = OkioStorage(fileSystem, testingSerializer) { fileInNonExistentDir }
             storage.createConnection().use { connection ->
@@ -294,7 +298,7 @@
 
     @Test
     fun writeToDirFails() = testScope.runTest {
-        val directoryFile = testIO.tempDir() / "this/is/a/directory"
+        val directoryFile = FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "this/is/a/directory"
         fileSystem.createDirectories(directoryFile)
 
         val storage = OkioStorage(fileSystem, testingSerializer) { directoryFile }
@@ -310,7 +314,7 @@
             if (failFileProducer) {
                 throw IOException("Exception when producing file")
             }
-            testIO.tempDir() / "new-temp-file.tmp"
+            FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "new-temp-file.tmp"
         }
         val storage = OkioStorage(fileSystem, testingSerializer, fileProducer)
 
@@ -327,13 +331,13 @@
 
     @Test
     fun writeAfterTransientBadRead() = testScope.runTest {
-        testingSerializer.failingRead = true
+        testingSerializerConfig.failingRead = true
 
         testConnection.writeData(1)
 
         assertThrows { testConnection.readData() }
 
-        testingSerializer.failingRead = false
+        testingSerializerConfig.failingRead = false
 
         testConnection.writeData(1)
         assertThat(testConnection.readData()).isEqualTo(1)
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt
deleted file mode 100644
index 9bcb6c8..0000000
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt
+++ /dev/null
@@ -1,61 +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.
- */
-
-package androidx.datastore.core.okio
-
-import androidx.datastore.core.Storage
-import kotlin.random.Random
-import okio.FileSystem
-import okio.Path
-
-// TODO: move to its own module
-class TestIO(private val dirName: String = "datastore-test-dir") {
-    var onProduceFileCallback: () -> Unit = {}
-    private val fileSystem = FileSystem.SYSTEM
-    private fun randomFileName( // LAME :)
-        prefix: String = "test-file"
-    ): String {
-        return prefix + (0 until 15).joinToString(separator = "") {
-            ('a' + Random.nextInt(from = 0, until = 26)).toString()
-        }
-    }
-    private val tmpDir = tempDir()
-
-    fun tempDir(): Path {
-        return FileSystem.SYSTEM_TEMPORARY_DIRECTORY / randomFileName(dirName)
-    }
-
-    fun  newFileStorage(
-        serializer: OkioSerializer,
-        prefix: String = "test-file"
-    ): Storage {
-        val storage = OkioStorage(
-            fileSystem = fileSystem,
-            serializer = serializer
-        ) {
-            onProduceFileCallback()
-            tmpDir / randomFileName(prefix)
-        }
-        return storage
-    }
-
-    fun  newFileStorage(
-        serializer: OkioSerializer,
-        testFile: Path
-    ): Storage {
-        return OkioStorage(fileSystem, serializer) { testFile }
-    }
-}
\ No newline at end of file
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt
deleted file mode 100644
index a120078..0000000
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt
+++ /dev/null
@@ -1,63 +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.
- */
-
-package androidx.datastore.core.okio
-
-import androidx.datastore.core.CorruptionException
-import okio.BufferedSink
-import okio.BufferedSource
-import okio.EOFException
-import okio.IOException
-import okio.use
-
-internal class TestingSerializer(
-    var failReadWithCorruptionException: Boolean = false,
-    var failingRead: Boolean = false,
-    var failingWrite: Boolean = false,
-    override val defaultValue: Byte = 0
-) : OkioSerializer {
-
-    override suspend fun readFrom(source: BufferedSource): Byte {
-        if (failReadWithCorruptionException) {
-            throw CorruptionException(
-                "CorruptionException",
-                IOException("I was asked to fail with corruption on reads")
-            )
-        }
-
-        if (failingRead) {
-            throw IOException("I was asked to fail on reads")
-        }
-
-        val read = try {
-            source.use {
-                it.readInt()
-            }
-        } catch (eof: EOFException) {
-            return 0
-        }
-        return read.toByte()
-    }
-
-    override suspend fun writeTo(t: Byte, sink: BufferedSink) {
-        if (failingWrite) {
-            throw IOException("I was asked to fail on writes")
-        }
-        sink.use {
-            it.writeInt(t.toInt())
-        }
-    }
-}
\ No newline at end of file
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index baaf645..4355020 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -55,6 +55,7 @@
                 implementation(libs.okio)
                 api(project(":datastore:datastore-core-okio"))
                 implementation(project(":internal-testutils-kmp"))
+                implementation(project(":internal-testutils-datastore"))
             }
         }
 
@@ -63,6 +64,7 @@
                 implementation(libs.junit)
                 implementation(libs.kotlinTest)
                 implementation(project(":internal-testutils-kmp"))
+                implementation(project(":internal-testutils-datastore"))
             }
         }
 
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
index 1c63e8c..3fc0429 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
@@ -16,6 +16,8 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.OkioPath
+import androidx.datastore.OkioTestIO
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.InternalCoroutinesApi
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
index d63ecc5..de8f95a 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestFile
+import androidx.datastore.TestIO
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlin.test.BeforeTest
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
index 18cd4a0..420ab81 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
@@ -18,6 +18,9 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestFile
+import androidx.datastore.TestIO
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.handlers.NoOpCorruptionHandler
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
diff --git a/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt b/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
index 64f1d8a..76523eb 100644
--- a/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
+++ b/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
@@ -17,6 +17,7 @@
 package androidx.datastore.core
 
 import androidx.annotation.GuardedBy
+import androidx.annotation.RestrictTo
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -27,7 +28,15 @@
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 
-internal class FileStorage(
+/**
+ * The Java IO File version of the Storage interface. Is able to read and write T to a given
+ * file location.
+ *
+ * @param serializer The serializer that can write  to and from a byte array.
+ * @param produceFile The file producer that returns the file that will be read and written.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class FileStorage(
     private val serializer: Serializer,
     private val produceFile: () -> File
 ) : Storage {
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
index 7037e6d..f6dcfbc 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
 import androidx.kruth.assertThat
 import java.io.File
@@ -71,7 +72,8 @@
 
         val store = DataStoreFactory.create(
             serializer = TestingSerializer(
-                TestingSerializerConfig(failReadWithCorruptionException = true)),
+                TestingSerializerConfig(failReadWithCorruptionException = true)
+            ),
             corruptionHandler = ReplaceFileCorruptionHandler {
                 valueToReplace
             },
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
index 72cf3fa..4cc1399 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import java.io.File
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
index e38dc1b..2910895 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
@@ -16,6 +16,8 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.FileTestIO
+import androidx.datastore.JavaIOFile
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
index 0e18d7f..abb75db 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
@@ -16,10 +16,10 @@
 
 package androidx.datastore.core.handlers
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.FileStorage
 import androidx.datastore.core.SingleProcessDataStore
 import androidx.datastore.core.TestingSerializer
-import androidx.datastore.core.TestingSerializerConfig
 import androidx.kruth.assertThrows
 import androidx.kruth.assertThat
 import java.io.File
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 45e2858..f3eac5e 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -238,6 +238,7 @@
 Found an unresolved type in androidx\.slice\.builders\$list\(android\.content\.Context, android\.net\.Uri, kotlin\.Long, kotlin\.Function[0-9]+\(\(androidx\.slice\.builders\.ListBuilderDsl, kotlin\.Unit\)\)\) \(ListBuilder\.kt:[0-9]+\)
 # See b/180023439 for hiltNavGraphViewModel warning.
 Unresolved link to .*
+Found an unresolved type in androidx\.core\.view\$removeItemAt\(android\.view\.Menu, kotlin\.Int\) \(Menu\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.foundation\.MutatorMutex\$mutate\(androidx\.compose\.foundation\.MutatePriority, kotlin\.coroutines\.SuspendFunction[0-9]+\(\(androidx\.compose\.foundation\.MutatorMutex\.mutate\.R\)\)\) \(MutatorMutex\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.foundation\.MutatorMutex\$mutateWith\(androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.T, androidx\.compose\.foundation\.MutatePriority, kotlin\.coroutines\.SuspendFunction[0-9]+\(\(androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.T, androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.R\)\)\) \(MutatorMutex\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.foundation\.gestures\$detectTapGestures\(androidx\.compose\.ui\.input\.pointer\.PointerInputScope, kotlin\.Function[0-9]+\(\(androidx\.compose\.ui\.geometry\.Offset, kotlin\.Unit\)\), kotlin\.Function[0-9]+\(\(androidx\.compose\.ui\.geometry\.Offset, kotlin\.Unit\)\), kotlin\.coroutines\.SuspendFunction[0-9]+\(\(androidx\.compose\.foundation\.gestures\.PressGestureScope, androidx\.compose\.ui\.geometry\.Offset, kotlin\.Unit\)\), kotlin\.Function[0-9]+\(\(androidx\.compose\.ui\.geometry\.Offset, kotlin\.Unit\)\)\) \(TapGestureDetector\.kt:[0-9]+\)
@@ -432,8 +433,7 @@
 # Linux builds cannot build mac, hence we get this warning.
 # see: https://github.com/JetBrains/kotlin/blob/master/native/commonizer/README.md
 # This warning is printed from: https://github.com/JetBrains/kotlin/blob/bc853e45e8982eff74e3263b0197c1af6086615d/native/commonizer/src/org/jetbrains/kotlin/commonizer/konan/LibraryCommonizer.kt#L41
-Warning\: No libraries found for target macos_arm[0-9]+\. This target will be excluded from commonization\.
-Warning\: No libraries found for target macos_x[0-9]+\. This target will be excluded from commonization\.
+Warning\: No libraries found for target (macos|ios|ios_simulator)_(arm|x)[0-9]+\. This target will be excluded from commonization\.
 # > Task :tv:tv-foundation:processDebugManifest
 # > Task :tv:tv-material:processDebugManifest
 # > Task :tv:tv-material:processReleaseManifest
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index f43fd20..5f4aeb0 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -16,8 +16,8 @@
     docs("androidx.ads:ads-identifier-provider:1.0.0-alpha04")
     docs("androidx.annotation:annotation:1.4.0")
     docs("androidx.annotation:annotation-experimental:1.2.0")
-    docs("androidx.appcompat:appcompat:1.5.0-alpha01")
-    docs("androidx.appcompat:appcompat-resources:1.5.0-alpha01")
+    docs("androidx.appcompat:appcompat:1.5.0-beta01")
+    docs("androidx.appcompat:appcompat-resources:1.5.0-beta01")
     docs("androidx.appsearch:appsearch:1.0.0-alpha04")
     docs("androidx.appsearch:appsearch-ktx:1.0.0-alpha04")
     docs("androidx.appsearch:appsearch-local-storage:1.0.0-alpha04")
@@ -130,10 +130,10 @@
     docs("androidx.drawerlayout:drawerlayout:1.1.1")
     docs("androidx.dynamicanimation:dynamicanimation:1.1.0-alpha02")
     docs("androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03")
-    docs("androidx.emoji2:emoji2:1.2.0-alpha04")
-    docs("androidx.emoji2:emoji2-bundled:1.2.0-alpha04")
-    docs("androidx.emoji2:emoji2-views:1.2.0-alpha04")
-    docs("androidx.emoji2:emoji2-views-helper:1.2.0-alpha04")
+    docs("androidx.emoji2:emoji2:1.2.0-beta01")
+    docs("androidx.emoji2:emoji2-bundled:1.2.0-beta01")
+    docs("androidx.emoji2:emoji2-views:1.2.0-beta01")
+    docs("androidx.emoji2:emoji2-views-helper:1.2.0-beta01")
     docs("androidx.emoji:emoji:1.2.0-alpha03")
     docs("androidx.emoji:emoji-appcompat:1.2.0-alpha03")
     docs("androidx.emoji:emoji-bundled:1.2.0-alpha03")
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index b406439..bae8cf7 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -1492,11 +1492,15 @@
 marker from an API is equivalent to adding the API to the current API surface.
 
 When transitioning an entire feature surface out of experimental, you *should*
-remove the definition for the associated annotation.
+remove the definition for the associated experimental marker annotation.
 
 When making any change to the experimental API surface, you *must* run
 `./gradlew updateApi` prior to uploading your change.
 
+NOTE Experimental marker annotation *are themselves* experimental, meaning that
+it's considered binary compatible to refactor or remove an experimental marker
+annotation.
+
 ### `@RestrictTo` APIs {#restricted-api}
 
 Jetpack's library tooling supports hiding Java-visible (ex. `public` and
diff --git a/docs/build_infrastructure.md b/docs/build_infrastructure.md
deleted file mode 100644
index 4408353..0000000
--- a/docs/build_infrastructure.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Build infrastructure
-
-
-
-[TOC]
-
-## Build invocation scripts
-
-AndroidX uses
-[`busytown/`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:busytown/)
-directory to wrap build bot invocations to a single command per build target.
-Each one of these scripts receives `DIST_DIR`, `OUT_DIR`, and `CHANGE_INFO`
-enviroment variables. `DIST_DIR` is a directory for putting build artifacts that
-should be saved. `OUT_DIR` is a directory to write temporary build outputs.
-`CHANGE_INFO` points to a file that allows build system to know what changed for
-a given change that is being built.
-
-## Gradle Remote Build Cache
-
-AndroidX build bots use the
-[Gradle Remote Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)
-to speed up build targets. We
-[configure](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/remoteBuildCache.gradle;drc=dd99f75742c18a499110b979c7c25bf822113e3e;l=49)
-the
-[GCP build cache Gradle plugin](https://github.com/androidx/gcp-gradle-build-cache)
-to connect to the GCP Storage bucket `androidx-gradle-remote-cache` in the
-`androidx-ge` project.
diff --git a/docs/faq.md b/docs/faq.md
index 6080e83..b4ea4da 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -6,13 +6,15 @@
 
 ### What is `androidx`?
 
-The Android Extension (`androidx`) Libraries provide functionality that extends
-the capabilities of the Android platform. These libraries, which ship separately
+Artifacts within the `androidx` package comprise the libraries of
+[Android Jetpack](https://developer.android.com/jetpack).
+
+Libraries in the `androidx` package provide functionality that extends the
+capabilities of the Android platform. These libraries, which ship separately
 from the Android OS, focus on improving the experience of developing apps
 through broad OS- and device-level compatibility, high-level abstractions to
 simplify and unify platform features, and other new features that target
-developer pain points. To find out more about `androidx`, see the public
-documentation on [developer.android.com](http://developer.android.com).
+developer pain points.
 
 ### Why did we move to `androidx`?
 
diff --git a/docs/gradle_enterprise.md b/docs/gradle_enterprise.md
deleted file mode 100644
index 19cd12d..0000000
--- a/docs/gradle_enterprise.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Gradle Enterprise
-
-
-
-[TOC]
-
-## Introduction
-
-Gradle Enterprise is used to speed up workflows that use GitHub actions for
-[playground](playground.md) projects.
-
-The Gradle Enterprise Server is running on AWS (`us-east-1`). This service is
-running on a Kubernetes cluster and can serve HTTP2 traffic. This service is
-managed by the Gradle team.
-
-The `FQHN` for the service is `hosted-ge-androidx.gradle.com` (port `443`).
-
-Note: The service will always use a `Location` header `ge.androidx.dev`. That is
-the only `hostname` it deems valid for a request origin.
diff --git a/docs/index.md b/docs/index.md
index 0a9bae4..a34d30b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -2,19 +2,6 @@
 
 ![alt_text](androidx.gif "Androidx Gif")
 
-## Ethos
-
-To create recommended components, tools, and guidance that makes it quick and
-easy to build great Android apps, including pieces both from Google and from
-trusted OSS sources.
-
-## Team mission
-
-To improve the Android developer experience by providing architectural guidance,
-addressing common pain points, and simplifying the app development process
-through broad compatibility across Android versions and elimination of
-boilerplate code so developers can focus on what makes their app special.
-
 ## What is `androidx`?
 
 Artifacts within the `androidx` package comprise the libraries of
@@ -27,17 +14,10 @@
 simplify and unify platform features, and other new features that target
 developer pain points.
 
-## What happened to the Support Library?
+For frequently asked questions, see the [General FAQ](faq.md).
 
-As part of the Jetpack project to improve developer experience on Android, the
-Support Library team undertook a massive refactoring project. Over the course of
-2017 and 2018, we streamlined and enforced consistency in our packaging,
-developed new policies around versioning and release, and developed tools to
-make it easy for developers to migrate.
-
-Revision `28.0.0` of the Support Library, which launched as stable in September
-2018, was the last feature release in the `android.support` package. There will
-be no further releases under Support Library packaging.
+To get started developing in AndroidX, see the [Getting started](onboarding.md)
+guide.
 
 ## Quick links
 
diff --git a/docs/onboarding.md b/docs/onboarding.md
index 7982b5c..34b7baf 100644
--- a/docs/onboarding.md
+++ b/docs/onboarding.md
@@ -12,10 +12,9 @@
 
 ## Workstation setup {#setup}
 
-You will need to install the
-[`repo`](https://source.android.com/setup/develop#repo) tool, which is used for
-Git branch and commit management. If you want to learn more about `repo`, see
-the [Repo Command Reference](https://source.android.com/setup/develop/repo).
+This section will help you install the `repo` tool, which is used for Git branch
+and commit management. If you want to learn more about `repo`, see the
+[Repo Command Reference](https://source.android.com/setup/develop/repo).
 
 ### Linux and MacOS {#setup-linux-mac}
 
@@ -138,7 +137,17 @@
 git config --global diff.renameLimit 999999
 ```
 
-### To check out older source, use the superproject
+### Set up Git file exclusions {#source-exclude}
+
+Mac users should consider adding `.DS_Store` to a global `.gitignore` file to
+avoid accidentally checking in local metadata files:
+
+```shell
+echo .DS_Store>>~/.gitignore
+git config --global core.excludesFile '~/.gitignore'
+```
+
+### To check out older sources, use the superproject {#source-historical}
 
 The
 [git superproject](https://android.googlesource.com/platform/superproject/+/androidx-main)
@@ -232,6 +241,9 @@
 > -Dsun.java2d.uiScale.enabled=false
 > ```
 
+> NOTE: We are aware of a bug where running `./studiow` does not result in
+> Android Studio application being launched.
+
 If in the future you encounter unexpected errors in Studio and you want to check
 for the possibility it is due to some incorrect settings or other generated
 files, you can run `./studiow --clean main ` or `./studiow
@@ -667,26 +679,60 @@
 #### Missing external dependency
 
 If Gradle cannot resolve a dependency listed in your `build.gradle`, you may
-need to import the corresponding artifact into `prebuilts/androidx/external`.
-Our workflow does not automatically download artifacts from the internet to
+need to import the corresponding artifact into one of the prebuilts
+repositories. These repositories are located under `prebuilts/androidx`. Our
+workflow does not automatically download artifacts from the internet to
 facilitate reproducible builds even if remote artifacts are changed.
 
-You can download a dependency by running:
+We use a script to download dependencies, you can learn more about it
+[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:development/importMaven/README.md).
 
-```shell
-cd frameworks/support && ./development/importMaven/import_maven_artifacts.py -n 'someGroupId:someArtifactId:someVersion'
-```
-
-This will create a change within the `prebuilts/androidx/external` directory.
-Make sure to upload this change before or concurrently (ex. in the same Gerrit
-topic) with the dependent library code.
+##### Importing dependencies in `libs.versions.toml`
 
 Libraries typically reference dependencies using constants defined in
-[`libs.versions.toml`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:gradle/libs.versions.toml),
-so please update this file to include a constant for the version of the library
-that you have checked in. You will reference this constant in your library's
+[`libs.versions.toml`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:gradle/libs.versions.toml).
+Update this file to include a constant for the version of the library that you
+want to depend on. You will reference this constant in your library's
 `build.gradle` dependencies.
 
+**After** you update the `libs.versions.toml` file with new dependencies, you
+can download them by running:
+
+```shell
+cd frameworks/support &&\
+development/importMaven/importMaven.sh import-toml
+```
+
+This command will resolve everything declared in the `libs.versions.toml` file
+and download missing artifacts into `prebuilts/androidx/external` or
+`prebuilts/androidx/internal`.
+
+Make sure to upload these changes before or concurrently (ex. in the same Gerrit
+topic) with the dependent library code.
+
+##### Downloading a dependency without changing `libs.versions.toml`
+
+You can also download a dependency without changing `libs.versions.toml` file by
+directly invoking:
+
+```shell
+cd frameworks/support &&\
+./development/importMaven/importMaven.sh someGroupId:someArtifactId:someVersion
+```
+
+##### Missing konan dependencies
+
+Kotlin Multiplatform projects need prebuilts to compile native code, which are
+located under `prebuilts/androidx/konan`. **After** you update the kotlin
+version of AndroidX, you should also download necessary prebuilts via:
+
+```shell
+cd frameworks/support &&\
+development/importMaven/importMaven.sh import-konan-binaries --konan-compiler-version 
+```
+
+Please remember to commit changes in the `prebuilts/androidx/konan` repository.
+
 #### Dependency verification
 
 If the new dependency you are importing is unsigned, or is signed with a new,
@@ -779,14 +825,6 @@
       ../../prebuilts/fullsdk-darwin/system-images
 ```
 
-### Benchmarking {#testing-benchmarking}
-
-Libraries are encouraged to write and monitor performance benchmarks. See the
-[Benchmarking](benchmarking.md) and [Macrobenchmarking](macrobenchmarking.md)
-pages for more details, and the
-[Benchmarking section of d.android.com](http://d.android.com/benchmark) for more
-info on these tools.
-
 ## Library snapshots {#snapshots}
 
 ### Quick how-to
diff --git a/docs/team.md b/docs/team.md
new file mode 100644
index 0000000..0c61aa4b
--- /dev/null
+++ b/docs/team.md
@@ -0,0 +1,47 @@
+# Core & Tooling Team
+
+go/androidx/team
+
+
+
+## What we do
+
+The AndroidX Core & Tooling team is responsible for the overall library
+development and release workflows, including defining and enforcing policies,
+owning the tools used for docs generation and API tracking, and ensuring the
+stability and performance of the build and test systems.
+
+## Team purpose
+
+Enable high-quality libraries so that app developers can succeed.
+
+## Team mission
+
+Define, enforce, and enable a high quality bar that provides best-in-class
+library development workflow for Jetpack libraries.
+
+## Team values
+
+-   Communication (open, honest, constructive)
+    -   Discussing issues in public (e.g. chat)
+    -   Providing a safe space to give/receive feedback
+    -   Documenting and sharing knowledge
+-   User Focus (meeting/exceeding our customer's needs and desires)
+    -   Listening to users, seeking out feedback
+    -   Taking user feedback seriously
+    -   Showing empathy for users
+-   Quality (strive for excellence, continuous improvement)
+    -   Prioritizing quality over velocity
+    -   Building long-lasting solutions
+    -   Following consistent, codified principles
+-   Integrity (honesty, sincerity, standing up for one’s beliefs)
+    -   Owning mistakes
+    -   Respecting others
+    -   Raising concerns when you have them!
+-   Responsibility (accountability)
+    -   Monitoring and maintaining our products
+    -   Providing clear ownership and escalation paths
+    -   Following up to ensure resolution of issues
diff --git a/docs/testability.md b/docs/testability.md
index b4f0918..45d4b6d 100644
--- a/docs/testability.md
+++ b/docs/testability.md
@@ -41,8 +41,9 @@
 To get started with sample code, see
 [Sample code in Kotlin modules](api_guidelines.md#sample-code-in-kotlin-modules)
 for information on writing samples that can be referenced from API reference
-documentation or [Project directory structure](api_guidelines.md#module-structure)
-for module naming guidelines if you'd like to create a basic test app.
+documentation or
+[Project directory structure](api_guidelines.md#module-structure) for module
+naming guidelines if you'd like to create a basic test app.
 
 ### Avoiding side-effects {#side-effects}
 
@@ -53,6 +54,25 @@
 avoid using singletons. If it is not possible, consider providing a test
 artifact that will reset the state of the singleton between tests.
 
+```java {.bad}
+public class JobQueue {
+  public static JobQueue getInstance();
+}
+```
+
+```java {.good}
+public class JobQueue {
+  public JobQueue();
+}
+```
+
+```kotlin {.good}
+object JobQueueTestUtil {
+     fun createForTest(): JobQueue
+     fun resetForTesting(jobQueue: JobQueue)
+}
+```
+
 #### Side effects due to external resources
 
 Sometimes, your library might be controlling resources on the device in which
@@ -63,6 +83,18 @@
 A possible alternative solution could be to provide a test rule that will
 properly close databases after each test.
 
+```java {.good}
+public class Camera {
+  // Sends configuration to the camera hardware, which persists the
+  // config across app restarts and applies to all camera clients.
+  public void setConfiguration(Config c);
+
+  // Retrieves the current configuration, which allows clients to
+  // restore the camera hardware to its prior state after testing.
+  public Config getConfiguration();
+}
+```
+
 If your library needs an inherently singleton resource (e.g. `WorkManager` is a
 wrapper around `JobScheduler` and there is only 1 instance of it provided by the
 system), consider providing a testing artifact. To provide isolation for tests,
@@ -85,31 +117,77 @@
 For example, the Room library allows developers to
 [pass different executors](https://developer.android.com/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor\(java.util.concurrent.Executor\))
 for background query operations. When writing a test, developers can invoke this
-with a custom executor where they can track work completion.
+with a custom executor where they can track work completion. See
+[SuspendingQueryTest](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672)
+in Room's integration test app for implementation details.
 
-*   [sample test](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672)
+```kotlin
+val localDatabase = Room.inMemoryDatabaseBuilder(
+    ApplicationProvider.getApplicationContext(), TestDatabase::class.java
+)
+    .setQueryExecutor(ArchTaskExecutor.getIOThreadExecutor())
+    .setTransactionExecutor(wrappedExecutor)
+    .build()
+
+// ...
+
+wrappedExecutor.awaitTermination(1, TimeUnit.SECONDS)
+```
+
+*   [sample test](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672)
 
 If the external resource you require does not make sense as a public API, such
 as a main thread executor, then you can provide a testing artifact which will
 allow setting it. For example, the Lifecycle package depends on the main thread
 executor to function but for an application, customizing it does not make sense
 (as there is only 1 "pre-defined" main thread for an app). For testing purposes,
-the Lifecycle library provides a testing artifact which includes
-[TestRules](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:arch/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java)
-to change them.
+the Lifecycle library provides a testing artifact which includes the
+[CountingTaskExecutorRule](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:arch/core/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java;l=36)
+JUnit test rule to change them.
+
+```kotlin
+@Rule
+@JvmField
+val countingTaskExecutorRule = CountingTaskExecutorRule()
+
+// ...
+
+@After
+fun teardown() {
+    // At the end of all tests, query executor should
+    // be idle (e.g. transaction thread released).
+    countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
+    assertThat(countingTaskExecutorRule.isIdle).isTrue()
+}
+```
 
 #### Fakes for external dependencies
 
 Sometimes, the developer might want to track side effects of your library for
-end to end testing. For instance, if your library provides some functionality
-that might decide to toggle bluetooth, outside developer's direct control, it
-might be a good idea to have an interface for that functionality and also
+end-to-end testing. For instance, if your library provides some functionality
+that might decide to toggle Bluetooth -- outside developer's direct control --
+it might be a good idea to have an interface for that functionality and also
 provide a fake that can record such calls. If you don't think that interface
-makes sense as a library configuration, you can
-[restrict](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java)
-that interface to your library group and provide a testing artifacts with the
-fake so that developer can observe side effects only in tests while you can
-avoid creating unnecessary APIs.
+makes sense as a library configuration, you can use the
+[@RestrictTo](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java)
+annotation with scope
+[LIBRARY_GROUP](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java;l=69)
+to restrict usage of that interface to your library group and provide a testing
+artifact with the fake so that developer can observe side effects only in tests
+while you can avoid creating unnecessary APIs.
+
+```kotlin
+public class EndpointConnector {
+  public void discoverEndpoints(Executor e, Consumer> callback);
+
+  @RestrictTo(Scope.LIBRARY_GROUP)
+  public void setBleInterface(BleInterface bleInterface);
+}
+
+public class EndpointConnectorTestHelper {
+  public void setBleInterface(EndpointConnector e, BleInterface b);
+}
+```
 
 NOTE There is a fine balance between making a library configurable and making
 configuration a nightmare for the developer. You should try to always have
@@ -124,17 +202,34 @@
 only make sense in the scope of testing. For example, a `Lifecycle` class has
 methods that are bound to the main thread but developers may want to have other
 tests that rely on lifecycle but do not run on the main thread (or even on an
-emulator). For such cases, you may create APIs that are testing only to allow
+emulator). For such cases, you may create APIs that are testing-only to allow
 developers to use them only in tests while giving them the confidence that it
 will behave as close as possible to a real implementation. For the case above,
 `LifecycleRegistry` provides an API to
-[create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=340)
+[create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334)
 an instance of it that will not enforce thread restrictions.
 
 NOTE Even though the implementation referenced above is acceptable, it is always
 better to create such functionality in additional testing artifacts when
 possible.
 
+When writing Android platform APIs, testing-only APIs should be clearly
+distinguished from non-test API surface and restricted as necessary to prevent
+misuse. In some cases, the `@TestApi` annotation may be appropriate to restrict
+usage to CTS tests; however, many platform testing APIs are also useful for app
+developers.
+
+```java {.good}
+class AdSelectionManager {
+  /**
+   * Returns testing-specific APIs for this manager.
+   *
+   * @throws SecurityException when called from a non-debuggable application
+   */
+  public TestAdSelectionManager getTestAdSelectionManager();
+}
+```
+
 ### Avoiding assumptions in app code for library behavior {#undefined-behavior}
 
 #### Provide fakes for common classes in a `-testing` artifact
@@ -146,21 +241,44 @@
 
 For such cases, it is a good practice to provide a fake implementation out of
 the box that can be controlled in tests. For example, the Lifecycle library
-provides a
-[fake implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt)
-for the `LifecycleOwner` class that can be manipulated in tests to create
-different use cases.
+provides
+[TestLifecycleOwner](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt)
+as a fake implementation for the `LifecycleOwner` class that can be manipulated
+in tests to create different use cases.
+
+```java
+private TestLifecycleOwner mOwner = new TestLifecycleOwner(
+    Lifecycle.State.INITIALIZED, new TestCoroutineDispatcher());
+
+@Test
+public void testObserverToggle() {
+    Observer observer = (Observer) mock(Observer.class);
+    mLiveData.observe(mOwner, observer);
+
+    verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+    // ...
+}
+```
 
 ## Document how to test with your library
 
 Even when your library is fully testable, it is often not clear for a developer
 to know which APIs are safe to call in tests and when. Providing guidance on
-[d.android.com](https://d.android.com) will make it much easier for the
-developer to start testing with your library.
+[d.android.com](https://d.android.com) or in a
+[Medium post](https://medium.com/androiddevelopers) will make it much easier for
+the developer to start testing with your library.
+
+Examples of testing guidance:
+
+-   [Integration tests with WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing)
+-   [Test and debug your Room database](https://developer.android.com/training/data-storage/room/testing-db)
+-   [Unit-testing LiveData and other common observability problems](https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04)
 
 ## Testability anti-patterns
 
-When writing tests against your APIs, look out for the following anti-patterns.
+When writing integration tests against your code that depends on your library,
+look out for the following anti-patterns.
 
 ### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier
 
@@ -178,7 +296,7 @@
 device.pressEnter();
 ```
 
-In apps with an active UI animation, the message queue is _never empty_. If the
+In apps with an active UI animation, the message queue is *never empty*. If the
 app is waiting for a callback across IPC, the message queue may be empty despite
 the test not reaching the desired state.
 
@@ -192,3 +310,24 @@
 
 See [Asynchronous work](api_guidelines.md#async) in the API Guidelines for more
 information on exposing the state of asynchronous work to clients.
+
+### Calling `Thread.sleep()` as a synchronization barrier
+
+`Thread.sleep()` is a common source of flakiness and instability in tests. If a
+developer needs to call `Thread.sleep()` -- even indirectly via a
+`PollingCheck` -- to get their test into a suitable state for checking
+assertions, your library needs to provide more reliable synchronization
+barriers.
+
+```java {.bad}
+List playlist = MediaTestUtils.createPlaylist(playlistSize);
+mPlayer.setPlaylist(playlist);
+
+// Wait some time for setting the playlist.
+Thread.sleep(TIMEOUT_MS);
+
+assertTrue(mPlayer.getPositionInPlaylist(), 0);
+```
+
+See the previous header for more information of providing synchronization
+barriers.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 630cc0f..a707641 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -30,11 +30,11 @@
 guavaJre = "31.1-jre"
 hilt = "2.42"
 incap = "0.2"
-kotlin = "1.7.0"
-kotlinNative = "1.7.0"
+kotlin = "1.7.10"
+kotlinNative = "1.7.10"
 kotlinCompileTesting = "1.4.1"
 kotlinCoroutines = "1.6.1"
-ksp = "1.7.0-1.0.6"
+ksp = "1.7.10-1.0.6"
 ktlint = "0.46.0-20220520.192227-74"
 leakcanary = "2.8.1"
 metalava = "1.0.0-alpha06"
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 0cbbb70..ea6f9642 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1238,21 +1238,21 @@
          
       
       
-      
-         
-            
+      
+         
+            
          
       
 
-      
-         
-            
+      
+         
+            
          
       
 
-      
-         
-            
+      
+         
+            
          
       
    
diff --git a/health/health-connect-client/api/current.txt b/health/health-connect-client/api/current.txt
index 57d6ee29..bef80bc 100644
--- a/health/health-connect-client/api/current.txt
+++ b/health/health-connect-client/api/current.txt
@@ -45,10 +45,10 @@
   public final class AggregationResult {
     method public operator boolean contains(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method public operator  T? get(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    method public java.util.List getDataOrigins();
+    method public java.util.Set getDataOrigins();
     method @Deprecated public  T? getMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method @Deprecated public boolean hasMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    property public final java.util.List dataOrigins;
+    property public final java.util.Set dataOrigins;
   }
 
   public final class AggregationResultGroupedByDuration {
@@ -162,7 +162,7 @@
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
-    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
+    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
     field public static final androidx.health.connect.client.records.BasalMetabolicRateRecord.Companion Companion;
   }
 
@@ -323,26 +323,18 @@
     field public static final String MEDIUM = "medium";
   }
 
-  public final class CyclingPedalingCadence {
-    ctor public CyclingPedalingCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
-    method public double getRevolutionsPerMinute();
-    method public java.time.Instant getTime();
-    property public final double revolutionsPerMinute;
-    property public final java.time.Instant time;
-  }
-
   public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.Record {
-    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Companion Companion;
@@ -354,6 +346,14 @@
   public static final class CyclingPedalingCadenceRecord.Companion {
   }
 
+  public static final class CyclingPedalingCadenceRecord.Sample {
+    ctor public CyclingPedalingCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
+    method public double getRevolutionsPerMinute();
+    method public java.time.Instant getTime();
+    property public final double revolutionsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class DistanceRecord implements androidx.health.connect.client.records.Record {
     ctor public DistanceRecord(androidx.health.connect.client.units.Length distance, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getDistance();
@@ -620,26 +620,18 @@
   public static final class FloorsClimbedRecord.Companion {
   }
 
-  public final class HeartRate {
-    ctor public HeartRate(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
-    method public long getBeatsPerMinute();
-    method public java.time.Instant getTime();
-    property public final long beatsPerMinute;
-    property public final java.time.Instant time;
-  }
-
   public final class HeartRateRecord implements androidx.health.connect.client.records.Record {
-    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.aggregate.AggregateMetric BPM_AVG;
@@ -652,6 +644,14 @@
   public static final class HeartRateRecord.Companion {
   }
 
+  public static final class HeartRateRecord.Sample {
+    ctor public HeartRateRecord.Sample(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class HeightRecord implements androidx.health.connect.client.records.Record {
     ctor public HeightRecord(androidx.health.connect.client.units.Length height, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getHeight();
@@ -1099,26 +1099,18 @@
     property public final java.time.Instant time;
   }
 
-  public final class StepsCadence {
-    ctor public StepsCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
-    method public double getRate();
-    method public java.time.Instant getTime();
-    property public final double rate;
-    property public final java.time.Instant time;
-  }
-
   public final class StepsCadenceRecord implements androidx.health.connect.client.records.Record {
-    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.StepsCadenceRecord.Companion Companion;
@@ -1130,6 +1122,14 @@
   public static final class StepsCadenceRecord.Companion {
   }
 
+  public static final class StepsCadenceRecord.Sample {
+    ctor public StepsCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
+    method public double getRate();
+    method public java.time.Instant getTime();
+    property public final double rate;
+    property public final java.time.Instant time;
+  }
+
   public final class StepsRecord implements androidx.health.connect.client.records.Record {
     ctor public StepsRecord(long count, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public long getCount();
@@ -1330,15 +1330,15 @@
 package androidx.health.connect.client.request {
 
   public final class AggregateGroupByDurationRequest {
-    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateGroupByPeriodRequest {
-    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateRequest {
-    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter);
+    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter);
   }
 
   public final class ChangesTokenRequest {
@@ -1346,7 +1346,7 @@
   }
 
   public final class ReadRecordsRequest {
-    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
+    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
   }
 
 }
diff --git a/health/health-connect-client/api/public_plus_experimental_current.txt b/health/health-connect-client/api/public_plus_experimental_current.txt
index 57d6ee29..bef80bc 100644
--- a/health/health-connect-client/api/public_plus_experimental_current.txt
+++ b/health/health-connect-client/api/public_plus_experimental_current.txt
@@ -45,10 +45,10 @@
   public final class AggregationResult {
     method public operator boolean contains(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method public operator  T? get(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    method public java.util.List getDataOrigins();
+    method public java.util.Set getDataOrigins();
     method @Deprecated public  T? getMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method @Deprecated public boolean hasMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    property public final java.util.List dataOrigins;
+    property public final java.util.Set dataOrigins;
   }
 
   public final class AggregationResultGroupedByDuration {
@@ -162,7 +162,7 @@
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
-    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
+    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
     field public static final androidx.health.connect.client.records.BasalMetabolicRateRecord.Companion Companion;
   }
 
@@ -323,26 +323,18 @@
     field public static final String MEDIUM = "medium";
   }
 
-  public final class CyclingPedalingCadence {
-    ctor public CyclingPedalingCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
-    method public double getRevolutionsPerMinute();
-    method public java.time.Instant getTime();
-    property public final double revolutionsPerMinute;
-    property public final java.time.Instant time;
-  }
-
   public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.Record {
-    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Companion Companion;
@@ -354,6 +346,14 @@
   public static final class CyclingPedalingCadenceRecord.Companion {
   }
 
+  public static final class CyclingPedalingCadenceRecord.Sample {
+    ctor public CyclingPedalingCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
+    method public double getRevolutionsPerMinute();
+    method public java.time.Instant getTime();
+    property public final double revolutionsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class DistanceRecord implements androidx.health.connect.client.records.Record {
     ctor public DistanceRecord(androidx.health.connect.client.units.Length distance, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getDistance();
@@ -620,26 +620,18 @@
   public static final class FloorsClimbedRecord.Companion {
   }
 
-  public final class HeartRate {
-    ctor public HeartRate(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
-    method public long getBeatsPerMinute();
-    method public java.time.Instant getTime();
-    property public final long beatsPerMinute;
-    property public final java.time.Instant time;
-  }
-
   public final class HeartRateRecord implements androidx.health.connect.client.records.Record {
-    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.aggregate.AggregateMetric BPM_AVG;
@@ -652,6 +644,14 @@
   public static final class HeartRateRecord.Companion {
   }
 
+  public static final class HeartRateRecord.Sample {
+    ctor public HeartRateRecord.Sample(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class HeightRecord implements androidx.health.connect.client.records.Record {
     ctor public HeightRecord(androidx.health.connect.client.units.Length height, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getHeight();
@@ -1099,26 +1099,18 @@
     property public final java.time.Instant time;
   }
 
-  public final class StepsCadence {
-    ctor public StepsCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
-    method public double getRate();
-    method public java.time.Instant getTime();
-    property public final double rate;
-    property public final java.time.Instant time;
-  }
-
   public final class StepsCadenceRecord implements androidx.health.connect.client.records.Record {
-    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.StepsCadenceRecord.Companion Companion;
@@ -1130,6 +1122,14 @@
   public static final class StepsCadenceRecord.Companion {
   }
 
+  public static final class StepsCadenceRecord.Sample {
+    ctor public StepsCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
+    method public double getRate();
+    method public java.time.Instant getTime();
+    property public final double rate;
+    property public final java.time.Instant time;
+  }
+
   public final class StepsRecord implements androidx.health.connect.client.records.Record {
     ctor public StepsRecord(long count, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public long getCount();
@@ -1330,15 +1330,15 @@
 package androidx.health.connect.client.request {
 
   public final class AggregateGroupByDurationRequest {
-    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateGroupByPeriodRequest {
-    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateRequest {
-    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter);
+    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter);
   }
 
   public final class ChangesTokenRequest {
@@ -1346,7 +1346,7 @@
   }
 
   public final class ReadRecordsRequest {
-    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
+    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
   }
 
 }
diff --git a/health/health-connect-client/api/restricted_current.txt b/health/health-connect-client/api/restricted_current.txt
index 27ca15d..c4fcb00 100644
--- a/health/health-connect-client/api/restricted_current.txt
+++ b/health/health-connect-client/api/restricted_current.txt
@@ -45,10 +45,10 @@
   public final class AggregationResult {
     method public operator boolean contains(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method public operator  T? get(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    method public java.util.List getDataOrigins();
+    method public java.util.Set getDataOrigins();
     method @Deprecated public  T? getMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
     method @Deprecated public boolean hasMetric(androidx.health.connect.client.aggregate.AggregateMetric metric);
-    property public final java.util.List dataOrigins;
+    property public final java.util.Set dataOrigins;
   }
 
   public final class AggregationResultGroupedByDuration {
@@ -162,7 +162,7 @@
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
-    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
+    field public static final androidx.health.connect.client.aggregate.AggregateMetric BASAL_CALORIES_TOTAL;
     field public static final androidx.health.connect.client.records.BasalMetabolicRateRecord.Companion Companion;
   }
 
@@ -323,26 +323,18 @@
     field public static final String MEDIUM = "medium";
   }
 
-  public final class CyclingPedalingCadence {
-    ctor public CyclingPedalingCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
-    method public double getRevolutionsPerMinute();
-    method public java.time.Instant getTime();
-    property public final double revolutionsPerMinute;
-    property public final java.time.Instant time;
-  }
-
-  public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.SeriesRecord {
-    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+  public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.SeriesRecord {
+    ctor public CyclingPedalingCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Companion Companion;
@@ -354,6 +346,14 @@
   public static final class CyclingPedalingCadenceRecord.Companion {
   }
 
+  public static final class CyclingPedalingCadenceRecord.Sample {
+    ctor public CyclingPedalingCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double revolutionsPerMinute);
+    method public double getRevolutionsPerMinute();
+    method public java.time.Instant getTime();
+    property public final double revolutionsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class DistanceRecord implements androidx.health.connect.client.records.IntervalRecord {
     ctor public DistanceRecord(androidx.health.connect.client.units.Length distance, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getDistance();
@@ -620,26 +620,18 @@
   public static final class FloorsClimbedRecord.Companion {
   }
 
-  public final class HeartRate {
-    ctor public HeartRate(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
-    method public long getBeatsPerMinute();
-    method public java.time.Instant getTime();
-    property public final long beatsPerMinute;
-    property public final java.time.Instant time;
-  }
-
-  public final class HeartRateRecord implements androidx.health.connect.client.records.SeriesRecord {
-    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+  public final class HeartRateRecord implements androidx.health.connect.client.records.SeriesRecord {
+    ctor public HeartRateRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.aggregate.AggregateMetric BPM_AVG;
@@ -652,6 +644,14 @@
   public static final class HeartRateRecord.Companion {
   }
 
+  public static final class HeartRateRecord.Sample {
+    ctor public HeartRateRecord.Sample(java.time.Instant time, @IntRange(from=1L, to=300L) long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
   public final class HeightRecord implements androidx.health.connect.client.records.InstantaneousRecord {
     ctor public HeightRecord(androidx.health.connect.client.units.Length height, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public androidx.health.connect.client.units.Length getHeight();
@@ -1122,26 +1122,18 @@
     property public final java.time.Instant time;
   }
 
-  public final class StepsCadence {
-    ctor public StepsCadence(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
-    method public double getRate();
-    method public java.time.Instant getTime();
-    property public final double rate;
-    property public final java.time.Instant time;
-  }
-
-  public final class StepsCadenceRecord implements androidx.health.connect.client.records.SeriesRecord {
-    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+  public final class StepsCadenceRecord implements androidx.health.connect.client.records.SeriesRecord {
+    ctor public StepsCadenceRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List samples, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public java.util.List getSamples();
+    method public java.util.List getSamples();
     method public java.time.Instant getStartTime();
     method public java.time.ZoneOffset? getStartZoneOffset();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public java.util.List samples;
+    property public java.util.List samples;
     property public java.time.Instant startTime;
     property public java.time.ZoneOffset? startZoneOffset;
     field public static final androidx.health.connect.client.records.StepsCadenceRecord.Companion Companion;
@@ -1153,6 +1145,14 @@
   public static final class StepsCadenceRecord.Companion {
   }
 
+  public static final class StepsCadenceRecord.Sample {
+    ctor public StepsCadenceRecord.Sample(java.time.Instant time, @FloatRange(from=0.0, to=10000.0) double rate);
+    method public double getRate();
+    method public java.time.Instant getTime();
+    property public final double rate;
+    property public final java.time.Instant time;
+  }
+
   public final class StepsRecord implements androidx.health.connect.client.records.IntervalRecord {
     ctor public StepsRecord(long count, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.records.metadata.Metadata metadata);
     method public long getCount();
@@ -1353,15 +1353,15 @@
 package androidx.health.connect.client.request {
 
   public final class AggregateGroupByDurationRequest {
-    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByDurationRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Duration timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateGroupByPeriodRequest {
-    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.List dataOriginFilter);
+    ctor public AggregateGroupByPeriodRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, java.time.Period timeRangeSlicer, optional java.util.Set dataOriginFilter);
   }
 
   public final class AggregateRequest {
-    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter);
+    ctor public AggregateRequest(java.util.Set> metrics, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter);
   }
 
   public final class ChangesTokenRequest {
@@ -1369,7 +1369,7 @@
   }
 
   public final class ReadRecordsRequest {
-    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.List dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
+    ctor public ReadRecordsRequest(kotlin.reflect.KClass recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, optional java.util.Set dataOriginFilter, optional boolean ascendingOrder, optional int pageSize, optional String? pageToken);
   }
 
 }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/aggregate/AggregationResult.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/aggregate/AggregationResult.kt
index 1c8ddae..0bd6c2b 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/aggregate/AggregationResult.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/aggregate/AggregationResult.kt
@@ -36,8 +36,8 @@
 internal constructor(
     internal val longValues: Map,
     internal val doubleValues: Map,
-    /** List of [DataOrigin]s that contributed to the aggregation result. */
-    public val dataOrigins: List
+    /** Set of [DataOrigin]s that contributed to the aggregation result. */
+    public val dataOrigins: Set
 ) {
 
     /**
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt
index db40939..150b677 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/aggregate/ProtoToAggregateDataRow.kt
@@ -57,5 +57,5 @@
     AggregationResult(
         longValues = longValuesMap,
         doubleValues = doubleValuesMap,
-        dataOrigins = dataOriginsList.map { DataOrigin(it.applicationId) }
+        dataOrigins = dataOriginsList.mapTo(HashSet()) { DataOrigin(it.applicationId) }
     )
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index f767c8b..41d9897 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -28,7 +28,6 @@
 import androidx.health.connect.client.records.BodyWaterMassRecord
 import androidx.health.connect.client.records.BoneMassRecord
 import androidx.health.connect.client.records.CervicalMucusRecord
-import androidx.health.connect.client.records.CyclingPedalingCadence
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
@@ -37,7 +36,6 @@
 import androidx.health.connect.client.records.ExerciseRepetitionsRecord
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
-import androidx.health.connect.client.records.HeartRate
 import androidx.health.connect.client.records.HeartRateRecord
 import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
@@ -64,7 +62,6 @@
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SleepStageRecord
 import androidx.health.connect.client.records.SpeedRecord
-import androidx.health.connect.client.records.StepsCadence
 import androidx.health.connect.client.records.StepsCadenceRecord
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.SwimmingStrokesRecord
@@ -171,7 +168,7 @@
                     endZoneOffset = endZoneOffset,
                     samples =
                         seriesValuesList.map { value ->
-                            CyclingPedalingCadence(
+                            CyclingPedalingCadenceRecord.Sample(
                                 time = Instant.ofEpochMilli(value.instantTimeMillis),
                                 revolutionsPerMinute = value.getDouble("rpm"),
                             )
@@ -186,7 +183,7 @@
                     endZoneOffset = endZoneOffset,
                     samples =
                         seriesValuesList.map { value ->
-                            HeartRate(
+                            HeartRateRecord.Sample(
                                 time = Instant.ofEpochMilli(value.instantTimeMillis),
                                 beatsPerMinute = value.getLong("bpm"),
                             )
@@ -357,7 +354,7 @@
                     endZoneOffset = endZoneOffset,
                     samples =
                         seriesValuesList.map { value ->
-                            StepsCadence(
+                            StepsCadenceRecord.Sample(
                                 time = Instant.ofEpochMilli(value.instantTimeMillis),
                                 rate = value.getDouble("rate"),
                             )
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/request/AggregateRequestToProto.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/request/AggregateRequestToProto.kt
index f3bfbd3..2f882268d 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/request/AggregateRequestToProto.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/request/AggregateRequestToProto.kt
@@ -52,5 +52,5 @@
         .setSlicePeriod(timeRangeSlicer.toString())
         .build()
 
-private fun List.toProtoList() =
+private fun Set.toProtoList() =
     this.map { DataProto.DataOrigin.newBuilder().setApplicationId(it.packageName).build() }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/BasalMetabolicRateRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/BasalMetabolicRateRecord.kt
index 156351e..4550a2b 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/BasalMetabolicRateRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/BasalMetabolicRateRecord.kt
@@ -17,6 +17,7 @@
 
 import androidx.health.connect.client.aggregate.AggregateMetric
 import androidx.health.connect.client.records.metadata.Metadata
+import androidx.health.connect.client.units.Energy
 import androidx.health.connect.client.units.Power
 import java.time.Instant
 import java.time.ZoneOffset
@@ -66,12 +67,12 @@
          * [androidx.health.connect.client.aggregate.AggregationResult].
          */
         @JvmField
-        val BASAL_CALORIES_TOTAL: AggregateMetric =
+        val BASAL_CALORIES_TOTAL: AggregateMetric =
             AggregateMetric.doubleMetric(
                 dataTypeName = BASAL_CALORIES_TYPE_NAME,
                 aggregationType = AggregateMetric.AggregationType.TOTAL,
                 fieldName = ENERGY_FIELD_NAME,
-                mapper = Power::kilocaloriesPerDay,
+                mapper = Energy::calories,
             )
     }
 }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/CyclingPedalingCadenceRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/CyclingPedalingCadenceRecord.kt
index c8ba7be..0c87da2 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/CyclingPedalingCadenceRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/CyclingPedalingCadenceRecord.kt
@@ -33,9 +33,9 @@
     override val startZoneOffset: ZoneOffset?,
     override val endTime: Instant,
     override val endZoneOffset: ZoneOffset?,
-    override val samples: List,
+    override val samples: List,
     override val metadata: Metadata = Metadata.EMPTY,
-) : SeriesRecord {
+) : SeriesRecord {
 
     /*
      * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
@@ -89,44 +89,44 @@
          */
         @JvmField val RPM_MAX: AggregateMetric = doubleMetric(TYPE, MAXIMUM, RPM_FIELD)
     }
-}
 
-/**
- * Represents a single measurement of the cycling pedaling cadence.
- *
- * @param time The point in time when the measurement was taken.
- * @param revolutionsPerMinute Cycling revolutions per minute. Valid range: 0-10000.
- *
- * @see CyclingPedalingCadenceRecord
- */
-public class CyclingPedalingCadence(
-    val time: Instant,
-    @FloatRange(from = 0.0, to = 10_000.0) val revolutionsPerMinute: Double,
-) {
-
-    init {
-        requireNonNegative(value = revolutionsPerMinute, name = "revolutionsPerMinute")
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+    /**
+     * Represents a single measurement of the cycling pedaling cadence.
+     *
+     * @param time The point in time when the measurement was taken.
+     * @param revolutionsPerMinute Cycling revolutions per minute. Valid range: 0-10000.
+     *
+     * @see CyclingPedalingCadenceRecord
      */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is CyclingPedalingCadence) return false
+    public class Sample(
+        val time: Instant,
+        @FloatRange(from = 0.0, to = 10_000.0) val revolutionsPerMinute: Double,
+    ) {
 
-        if (time != other.time) return false
-        if (revolutionsPerMinute != other.revolutionsPerMinute) return false
+        init {
+            requireNonNegative(value = revolutionsPerMinute, name = "revolutionsPerMinute")
+        }
 
-        return true
-    }
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Sample) return false
 
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun hashCode(): Int {
-        var result = time.hashCode()
-        result = 31 * result + revolutionsPerMinute.hashCode()
-        return result
+            if (time != other.time) return false
+            if (revolutionsPerMinute != other.revolutionsPerMinute) return false
+
+            return true
+        }
+
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun hashCode(): Int {
+            var result = time.hashCode()
+            result = 31 * result + revolutionsPerMinute.hashCode()
+            return result
+        }
     }
 }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateRecord.kt
index b16f820..bd488fe 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateRecord.kt
@@ -27,9 +27,9 @@
     override val startZoneOffset: ZoneOffset?,
     override val endTime: Instant,
     override val endZoneOffset: ZoneOffset?,
-    override val samples: List,
+    override val samples: List,
     override val metadata: Metadata = Metadata.EMPTY,
-) : SeriesRecord {
+) : SeriesRecord {
 
     /*
      * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
@@ -100,44 +100,44 @@
         val MEASUREMENTS_COUNT: AggregateMetric =
             AggregateMetric.countMetric(HEART_RATE_TYPE_NAME)
     }
-}
 
-/**
- * Represents a single measurement of the heart rate.
- *
- * @param time The point in time when the measurement was taken.
- * @param beatsPerMinute Heart beats per minute. Validation range: 1-300.
- *
- * @see HeartRateRecord
- */
-public class HeartRate(
-    val time: Instant,
-    @androidx.annotation.IntRange(from = 1, to = 300) val beatsPerMinute: Long,
-) {
-
-    init {
-        requireNonNegative(value = beatsPerMinute, name = "beatsPerMinute")
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+    /**
+     * Represents a single measurement of the heart rate.
+     *
+     * @param time The point in time when the measurement was taken.
+     * @param beatsPerMinute Heart beats per minute. Validation range: 1-300.
+     *
+     * @see HeartRateRecord
      */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRate) return false
+    public class Sample(
+        val time: Instant,
+        @androidx.annotation.IntRange(from = 1, to = 300) val beatsPerMinute: Long,
+    ) {
 
-        if (time != other.time) return false
-        if (beatsPerMinute != other.beatsPerMinute) return false
+        init {
+            requireNonNegative(value = beatsPerMinute, name = "beatsPerMinute")
+        }
 
-        return true
-    }
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Sample) return false
 
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun hashCode(): Int {
-        var result = time.hashCode()
-        result = 31 * result + beatsPerMinute.hashCode()
-        return result
+            if (time != other.time) return false
+            if (beatsPerMinute != other.beatsPerMinute) return false
+
+            return true
+        }
+
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun hashCode(): Int {
+            var result = time.hashCode()
+            result = 31 * result + beatsPerMinute.hashCode()
+            return result
+        }
     }
 }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/MealType.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/MealType.kt
index c4a2852..40299d1 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/MealType.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/MealType.kt
@@ -20,9 +20,13 @@
 /** Type of meal. */
 public object MealType {
     const val UNKNOWN = "unknown"
+    /** Use this for the first meal of the day, usually the morning meal. */
     const val BREAKFAST = "breakfast"
+    /** Use this for the noon meal. */
     const val LUNCH = "lunch"
+    /** Use this for last meal of the day, usually the evening meal. */
     const val DINNER = "dinner"
+    /** Any meal outside of the usual three meals per day. */
     const val SNACK = "snack"
 }
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/StepsCadenceRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/StepsCadenceRecord.kt
index b10ccfe..d7488b6 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/StepsCadenceRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/StepsCadenceRecord.kt
@@ -31,9 +31,9 @@
     override val startZoneOffset: ZoneOffset?,
     override val endTime: Instant,
     override val endZoneOffset: ZoneOffset?,
-    override val samples: List,
+    override val samples: List,
     override val metadata: Metadata = Metadata.EMPTY,
-) : SeriesRecord {
+) : SeriesRecord {
 
     /*
      * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
@@ -87,42 +87,42 @@
          */
         @JvmField val RATE_MAX: AggregateMetric = doubleMetric(TYPE, MAXIMUM, RATE_FIELD)
     }
-}
 
-/**
- * Represents a single measurement of the steps cadence.
- *
- * @param time The point in time when the measurement was taken.
- * @param rate Rate in steps per minute. Valid range: 0-10000.
- */
-class StepsCadence(
-    val time: Instant,
-    @FloatRange(from = 0.0, to = 10_000.0) val rate: Double,
-) {
-
-    init {
-        requireNonNegative(value = rate, name = "rate")
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+    /**
+     * Represents a single measurement of the steps cadence.
+     *
+     * @param time The point in time when the measurement was taken.
+     * @param rate Rate in steps per minute. Valid range: 0-10000.
      */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is StepsCadence) return false
+    class Sample(
+        val time: Instant,
+        @FloatRange(from = 0.0, to = 10_000.0) val rate: Double,
+    ) {
 
-        if (time != other.time) return false
-        if (rate != other.rate) return false
+        init {
+            requireNonNegative(value = rate, name = "rate")
+        }
 
-        return true
-    }
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Sample) return false
 
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun hashCode(): Int {
-        var result = time.hashCode()
-        result = 31 * result + rate.hashCode()
-        return result
+            if (time != other.time) return false
+            if (rate != other.rate) return false
+
+            return true
+        }
+
+        /*
+         * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
+         */
+        override fun hashCode(): Int {
+            var result = time.hashCode()
+            result = 31 * result + rate.hashCode()
+            return result
+        }
     }
 }
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByDurationRequest.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByDurationRequest.kt
index 8c09819..7650981 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByDurationRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByDurationRequest.kt
@@ -24,15 +24,19 @@
  * Request object to read time bucketed aggregations for given [AggregateMetric]s in Android Health
  * Platform.
  *
+ * [timeRangeSlicer] contains a [Duration] of fixed physical time intervals, such as per hour, per
+ * ten minutes or so. Prefer [AggregateGroupByPeriodRequest], if you would like variable length time
+ * intervals, such as per day, which may or may not include DST (23 or 25 hour).
+ *
  * @param metrics Set of [AggregateMetric]s to aggregate, such as `Steps::STEPS_COUNT_TOTAL`.
  * @param timeRangeFilter The [TimeRangeFilter] to read from.
  * @param timeRangeSlicer The bucket size of each returned aggregate row. [timeRangeFilter] will be
  * sliced into several equal-sized time buckets (except for the last one).
- * @param dataOriginFilter List of [DataOrigin]s to read from, or empty for no filter.
+ * @param dataOriginFilter Set of [DataOrigin]s to read from, or empty for no filter.
  */
 class AggregateGroupByDurationRequest(
     internal val metrics: Set>,
     internal val timeRangeFilter: TimeRangeFilter,
     internal val timeRangeSlicer: Duration,
-    internal val dataOriginFilter: List = emptyList(),
+    internal val dataOriginFilter: Set = emptySet(),
 )
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByPeriodRequest.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByPeriodRequest.kt
index bae6286..c95d4e3 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByPeriodRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateGroupByPeriodRequest.kt
@@ -24,15 +24,19 @@
  * Request object to read time bucketed aggregations for given [AggregateMetric]s in Android Health
  * Platform.
  *
+ * [timeRangeSlicer] contains a [Period] of variable length time intervals, such as per day, which
+ * may be anywhere between 23, 24, or 25 hour. Use [AggregateGroupByDurationRequest] if time slice
+ * is of fixed intervals, such as an hour, every ten minutes.
+ *
  * @param metrics Set of [AggregateMetric]s to aggregate, such as `Steps::STEPS_COUNT_TOTAL`.
  * @param timeRangeFilter The [TimeRangeFilter] to read from.
  * @param timeRangeSlicer The bucket size of each returned aggregate row. [timeRangeFilter] will be
  * sliced into several equal-sized time buckets (except for the last one).
- * @param dataOriginFilter List of [DataOrigin]s to read from, or empty for no filter.
+ * @param dataOriginFilter Set of [DataOrigin]s to read from, or empty for no filter.
  */
 class AggregateGroupByPeriodRequest(
     internal val metrics: Set>,
     internal val timeRangeFilter: TimeRangeFilter,
     internal val timeRangeSlicer: Period,
-    internal val dataOriginFilter: List = emptyList(),
+    internal val dataOriginFilter: Set = emptySet(),
 )
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateRequest.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateRequest.kt
index 10ddc27..f578c8f 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/AggregateRequest.kt
@@ -24,10 +24,10 @@
  *
  * @param metrics Set of [AggregateMetric]s to aggregate, such as `Steps::STEPS_COUNT_TOTAL`.
  * @param timeRangeFilter The [TimeRangeFilter] to read from.
- * @param dataOriginFilter List of [DataOrigin]s to read from, or empty for no filter.
+ * @param dataOriginFilter Set of [DataOrigin]s to read from, or empty for no filter.
  */
 class AggregateRequest(
     internal val metrics: Set>,
     internal val timeRangeFilter: TimeRangeFilter,
-    internal val dataOriginFilter: List = emptyList(),
+    internal val dataOriginFilter: Set = emptySet(),
 )
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/ReadRecordsRequest.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/ReadRecordsRequest.kt
index cf16e51..e328742 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/request/ReadRecordsRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/request/ReadRecordsRequest.kt
@@ -47,7 +47,7 @@
 public class ReadRecordsRequest(
     internal val recordType: KClass,
     internal val timeRangeFilter: TimeRangeFilter,
-    internal val dataOriginFilter: List = emptyList(),
+    internal val dataOriginFilter: Set = emptySet(),
     internal val ascendingOrder: Boolean = true,
     internal val pageSize: Int = 1000,
     internal val pageToken: String? = null,
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/data/ProtoParcelable.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/data/ProtoParcelable.kt
index 1a74b65..b53d47d 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/data/ProtoParcelable.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/data/ProtoParcelable.kt
@@ -66,7 +66,7 @@
          * Constructs and returns a [Creator] based on the provided [parser] accepting a [ByteArray]
          * .
          */
-        inline fun > newCreator(
+        internal inline fun > newCreator(
             crossinline parser: (ByteArray) -> U
         ): Creator {
             return object : Creator {
@@ -93,10 +93,10 @@
 }
 
 /** Flag marking that a proto is stored as an in-place `byte[]` array. */
-const val STORE_IN_PLACE = 0
+internal const val STORE_IN_PLACE = 0
 
 /** Flag marking that a proto is stored in [SharedMemory]. */
-const val STORE_SHARED_MEMORY = 1
+internal const val STORE_SHARED_MEMORY = 1
 
 /** Maximum size of a proto stored as an in-place `byte[]` array (16 KiB). */
 private const val MAX_IN_PLACE_SIZE = 16 * 1024
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/error/ErrorStatusConverter.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/error/ErrorStatusConverter.kt
index 6e6af7c..8a72c1d 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/error/ErrorStatusConverter.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/error/ErrorStatusConverter.kt
@@ -22,7 +22,7 @@
 import java.io.IOException
 import java.lang.IllegalArgumentException
 
-val errorCodeExceptionMap =
+internal val errorCodeExceptionMap =
     mapOf(
         ErrorCode.PROVIDER_NOT_INSTALLED to java.lang.UnsupportedOperationException::class,
         ErrorCode.PROVIDER_NOT_ENABLED to java.lang.UnsupportedOperationException::class,
@@ -41,7 +41,7 @@
     )
 
 @Suppress("ObsoleteSdkInt") // We want to target lower down to 14 in the future.
-fun ErrorStatus.toException(): Exception {
+internal fun ErrorStatus.toException(): Exception {
     errorCodeExceptionMap[this.errorCode]?.let {
         return when (it) {
             SecurityException::class -> SecurityException(this.errorMessage)
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/ipc/Client.java b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/ipc/Client.java
index d067ed5..58e2fa1 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/ipc/Client.java
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/ipc/Client.java
@@ -99,7 +99,8 @@
      *
      * @see #execute(RemoteFutureOperation)
      */
-    protected @NonNull  ListenableFuture execute(@NonNull RemoteOperation operation) {
+    @NonNull
+    protected  ListenableFuture execute(@NonNull RemoteOperation operation) {
         return execute((service, resultFuture) -> resultFuture.set(operation.execute(service)));
     }
 
@@ -111,7 +112,8 @@
      * @return {@link ListenableFuture} with the result of the operation or an exception if the
      *     execution fails.
      */
-    protected @NonNull  ListenableFuture execute(
+    @NonNull
+    protected  ListenableFuture execute(
             @NonNull RemoteFutureOperation operation) {
         SettableFuture settableFuture = SettableFuture.create();
         mConnectionManager.scheduleForExecution(createQueueOperation(operation, settableFuture));
@@ -127,7 +129,8 @@
      * @return {@link ListenableFuture} with the result of the operation or an exception if the
      *     execution fails or if the remote service version is lower than {@code minApiVersion}
      */
-    protected @NonNull  ListenableFuture executeWithVersionCheck(
+    @NonNull
+    protected  ListenableFuture executeWithVersionCheck(
             int minApiVersion, @NonNull RemoteFutureOperation operation) {
         SettableFuture settableFuture = SettableFuture.create();
         ListenableFuture versionFuture =
@@ -167,7 +170,8 @@
      *
      * 

If current version is available from earlier calls, it would return the value from cache. */ - protected @NonNull ListenableFuture getCurrentRemoteVersion(boolean forceRefresh) { + @NonNull + protected ListenableFuture getCurrentRemoteVersion(boolean forceRefresh) { if (mCurrentVersion == UNKNOWN_VERSION || forceRefresh) { return Futures.transform( execute(mRemoteVersionGetter), @@ -194,7 +198,8 @@ * @return {@link ListenableFuture} with the result of the operation or an exception if the * execution fails */ - protected @NonNull ListenableFuture registerListener( + @NonNull + protected ListenableFuture registerListener( @NonNull ListenerKey listenerKey, @NonNull RemoteOperation registerListenerOperation ) { @@ -217,7 +222,8 @@ * @return {@link ListenableFuture} with the result of the operation or an exception if the * execution fails */ - protected @NonNull ListenableFuture registerListener( + @NonNull + protected ListenableFuture registerListener( @NonNull ListenerKey listenerKey, @NonNull RemoteFutureOperation registerListenerOperation ) { @@ -237,7 +243,8 @@ * @return {@link ListenableFuture} with the result of the operation or an exception if the * execution fails */ - protected @NonNull ListenableFuture unregisterListener( + @NonNull + protected ListenableFuture unregisterListener( @NonNull ListenerKey listenerKey, @NonNull RemoteOperation unregisterListenerOperation ) { @@ -257,7 +264,8 @@ * @return {@link ListenableFuture} with the result of the operation or an exception if the * execution fails */ - protected @NonNull ListenableFuture unregisterListener( + @NonNull + protected ListenableFuture unregisterListener( @NonNull ListenerKey listenerKey, @NonNull RemoteFutureOperation unregisterListenerOperation ) { @@ -267,7 +275,8 @@ return settableFuture; } - protected @NonNull Exception getApiVersionCheckFailureException( + @NonNull + protected Exception getApiVersionCheckFailureException( int currentVersion, int minApiVersion) { return new ApiVersionException(currentVersion, minApiVersion); }

diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/foregroundstate/package-info.java
similarity index 61%
copy from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
copy to health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/foregroundstate/package-info.java
index 3bd6754..5c17428 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/foregroundstate/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 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.
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+package androidx.health.platform.client.impl.permission.foregroundstate;
 
-import kotlin.jvm.Volatile
-
-data class TestingSerializerConfig(
-    @Volatile var failReadWithCorruptionException: Boolean = false,
-    @Volatile var failingRead: Boolean = false,
-    @Volatile var failingWrite: Boolean = false,
-    @Volatile var defaultValue: Byte = 0
-)
\ No newline at end of file
+import androidx.annotation.RestrictTo;
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/token/package-info.java
similarity index 61%
copy from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
copy to health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/token/package-info.java
index 3bd6754..7dc9361 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/permission/token/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 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.
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+package androidx.health.platform.client.impl.permission.token;
 
-import kotlin.jvm.Volatile
-
-data class TestingSerializerConfig(
-    @Volatile var failReadWithCorruptionException: Boolean = false,
-    @Volatile var failingRead: Boolean = false,
-    @Volatile var failingWrite: Boolean = false,
-    @Volatile var defaultValue: Byte = 0
-)
\ No newline at end of file
+import androidx.annotation.RestrictTo;
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/sdkservice/package-info.java
similarity index 61%
copy from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
copy to health/health-connect-client/src/main/java/androidx/health/platform/client/impl/sdkservice/package-info.java
index 3bd6754..849ce71 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/impl/sdkservice/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 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.
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+package androidx.health.platform.client.impl.sdkservice;
 
-import kotlin.jvm.Volatile
-
-data class TestingSerializerConfig(
-    @Volatile var failReadWithCorruptionException: Boolean = false,
-    @Volatile var failingRead: Boolean = false,
-    @Volatile var failingWrite: Boolean = false,
-    @Volatile var defaultValue: Byte = 0
-)
\ No newline at end of file
+import androidx.annotation.RestrictTo;
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/permission/Permission.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/permission/Permission.kt
index 919e286..359ea2a 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/permission/Permission.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/permission/Permission.kt
@@ -19,7 +19,11 @@
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.PermissionProto
 
-/** Internal parcelable wrapper over proto object. */
+/**
+ * Internal parcelable wrapper over proto object.
+ *
+ * @suppress
+ */
 class Permission(override val proto: PermissionProto.Permission) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/AggregateDataRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/AggregateDataRequest.kt
index 5aa463f..f2eaf86 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/AggregateDataRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/AggregateDataRequest.kt
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
-/** Internal wrapper to help transfer protos over ipc. */
+/**
+ * Internal wrapper to help transfer protos over ipc.
+ * @suppress
+ */
 class AggregateDataRequest(override val proto: RequestProto.AggregateDataRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRangeRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRangeRequest.kt
index c203971..e0803f9 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRangeRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRangeRequest.kt
@@ -19,7 +19,10 @@
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
-/** Internal parcelable for IPC calls. */
+/**
+ * Internal parcelable for IPC calls.
+ * @suppress
+ */
 class DeleteDataRangeRequest(override val proto: RequestProto.DeleteDataRangeRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRequest.kt
index b144b84..8421c162 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/DeleteDataRequest.kt
@@ -13,13 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
-/** Internal parcelable for IPC calls. */
+/**
+ * Internal parcelable for IPC calls.
+ *
+ * @suppress
+ */
 class DeleteDataRequest(
     val uids: List,
     val clientIds: List
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesRequest.kt
index ddc7591..604c5ed 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesRequest.kt
@@ -19,6 +19,7 @@
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
+/** @suppress */
 class GetChangesRequest(override val proto: RequestProto.GetChangesRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesTokenRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesTokenRequest.kt
index 45cd5d4..57f4316 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesTokenRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/GetChangesTokenRequest.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
+/** @suppress */
 class GetChangesTokenRequest(override val proto: RequestProto.GetChangesTokenRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRangeRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRangeRequest.kt
index 8a8d0f8..a6f0203 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRangeRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRangeRequest.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
+/** @suppress */
 class ReadDataRangeRequest(override val proto: RequestProto.ReadDataRangeRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRequest.kt
index 561bd42..5d70427 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/ReadDataRequest.kt
@@ -13,13 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
-/** Internal parcelable for IPC calls. */
+/**
+ * Internal parcelable for IPC calls.
+ *
+ * @suppress
+ */
 class ReadDataRequest(override val proto: RequestProto.ReadDataRequest) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/RequestContext.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/RequestContext.kt
index 1bfe050..48ffebe2 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/RequestContext.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/RequestContext.kt
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.RequestProto
 
-/** Data object holding context data for IPC calls. */
+/**
+ * Data object holding context data for IPC calls.
+ * @suppress
+ */
 class RequestContext(
     val callingPackage: String,
     val sdkVersion: Int,
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/UpsertDataRequest.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/UpsertDataRequest.kt
index fa23fcc..9851795 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/request/UpsertDataRequest.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/request/UpsertDataRequest.kt
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.request
 
 import android.os.Parcelable
@@ -20,7 +21,11 @@
 import androidx.health.platform.client.proto.DataProto
 import androidx.health.platform.client.proto.RequestProto
 
-/** Internal parcelable for IPC calls. */
+/**
+ * Internal parcelable for IPC calls.
+ *
+ * @suppress
+ */
 class UpsertDataRequest(val dataPoints: List) :
     ProtoParcelable() {
     override val proto: RequestProto.UpsertDataRequest
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/AggregateDataResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/AggregateDataResponse.kt
index 86320ce..00f9356 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/AggregateDataResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/AggregateDataResponse.kt
@@ -13,13 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.response
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
-/** Internal wrapper to help transfer protos over ipc. */
+/**
+ * Internal wrapper to help transfer protos over ipc.
+ *
+ * @suppress
+ */
 class AggregateDataResponse(override val proto: ResponseProto.AggregateDataResponse) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesResponse.kt
index 5c1c7bf..59c684c3 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesResponse.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.response
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
+/** @suppress */
 class GetChangesResponse(override val proto: ResponseProto.GetChangesResponse) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesTokenResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesTokenResponse.kt
index b2f6069..7d232a6 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesTokenResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/GetChangesTokenResponse.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.response
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
+/** @suppress */
 class GetChangesTokenResponse(override val proto: ResponseProto.GetChangesTokenResponse) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/InsertDataResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/InsertDataResponse.kt
index 99f33c7..af5d80d 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/InsertDataResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/InsertDataResponse.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.response
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
+/** @suppress */
 class InsertDataResponse(val dataPointUids: List) :
     ProtoParcelable() {
     override val proto: ResponseProto.InsertDataResponse
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataRangeResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataRangeResponse.kt
index 9ec63bc..97652ab 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataRangeResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataRangeResponse.kt
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.health.platform.client.response
 
 import android.os.Parcelable
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
+/** @suppress */
 class ReadDataRangeResponse(override val proto: ResponseProto.ReadDataRangeResponse) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataResponse.kt b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataResponse.kt
index 5966c1e..5f26162 100644
--- a/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataResponse.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/platform/client/response/ReadDataResponse.kt
@@ -19,6 +19,7 @@
 import androidx.health.platform.client.impl.data.ProtoParcelable
 import androidx.health.platform.client.proto.ResponseProto
 
+/** @suppress */
 class ReadDataResponse(override val proto: ResponseProto.ReadDataResponse) :
     ProtoParcelable() {
 
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index ec02294..9df3d3c 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -22,10 +22,9 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import java.lang.IllegalStateException
-import java.lang.UnsupportedOperationException
 import org.junit.Assert.assertThrows
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.Shadows
@@ -43,6 +42,7 @@
         context = ApplicationProvider.getApplicationContext()
     }
 
+    @Ignore // b/238635208
     @Test
     fun noBackingImplementation_unavailable() {
         val packageManager = context.packageManager
@@ -54,6 +54,7 @@
         }
     }
 
+    @Ignore // b/238635208
     @Test
     fun backingImplementation_notEnabled_unavailable() {
         installPackage(context, PROVIDER_PACKAGE_NAME, enabled = false)
@@ -64,6 +65,7 @@
         }
     }
 
+    @Ignore // b/238635208
     @Test
     fun backingImplementation_enabled_isAvailable() {
         installPackage(context, PROVIDER_PACKAGE_NAME, enabled = true)
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/aggregate/AggregationResultGroupedByPeriodTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/aggregate/AggregationResultGroupedByPeriodTest.kt
index 359c6ef..7bb1ebe 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/aggregate/AggregationResultGroupedByPeriodTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/aggregate/AggregationResultGroupedByPeriodTest.kt
@@ -28,7 +28,7 @@
     fun constructor_endTimeNotAfterStartTime_throws() {
         assertThrows(IllegalArgumentException::class.java) {
             AggregationResultGroupedByPeriod(
-                result = AggregationResult(mapOf(), mapOf(), listOf()),
+                result = AggregationResult(mapOf(), mapOf(), setOf()),
                 startTime = LocalDateTime.parse("2022-02-22T20:22:02"),
                 endTime = LocalDateTime.parse("2022-02-11T20:22:02"),
             )
@@ -36,14 +36,14 @@
 
         assertThrows(IllegalArgumentException::class.java) {
             AggregationResultGroupedByPeriod(
-                result = AggregationResult(mapOf(), mapOf(), listOf()),
+                result = AggregationResult(mapOf(), mapOf(), setOf()),
                 startTime = LocalDateTime.parse("2022-02-11T20:22:02"),
                 endTime = LocalDateTime.parse("2022-02-11T20:22:02"),
             )
         }
 
         AggregationResultGroupedByPeriod(
-            result = AggregationResult(mapOf(), mapOf(), listOf()),
+            result = AggregationResult(mapOf(), mapOf(), setOf()),
             startTime = LocalDateTime.parse("2022-02-11T20:22:02"),
             endTime = LocalDateTime.parse("2022-02-22T20:22:02"),
         )
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/aggregate/AggregationResultConverterTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/aggregate/AggregationResultConverterTest.kt
index 414a2de..571db2b 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/aggregate/AggregationResultConverterTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/aggregate/AggregationResultConverterTest.kt
@@ -46,7 +46,7 @@
                 AggregationResult(
                     longValues = mapOf(Pair("longKey", 567L)),
                     doubleValues = mapOf(Pair("doubleKey", 123.4)),
-                    dataOrigins = listOf(DataOrigin("testApp")),
+                    dataOrigins = setOf(DataOrigin("testApp")),
                 )
             )
     }
@@ -73,7 +73,7 @@
                         AggregationResult(
                             longValues = mapOf(Pair("longKey", 567L)),
                             doubleValues = mapOf(Pair("doubleKey", 123.4)),
-                            dataOrigins = listOf(DataOrigin("testApp")),
+                            dataOrigins = setOf(DataOrigin("testApp")),
                         ),
                     startTime = Instant.ofEpochMilli(1111),
                     endTime = Instant.ofEpochMilli(9999),
@@ -129,7 +129,7 @@
                         AggregationResult(
                             longValues = mapOf(Pair("longKey", 567L)),
                             doubleValues = mapOf(Pair("doubleKey", 123.4)),
-                            dataOrigins = listOf(DataOrigin("testApp")),
+                            dataOrigins = setOf(DataOrigin("testApp")),
                         ),
                     startTime = LocalDateTime.parse("2022-02-11T20:22:02"),
                     endTime = LocalDateTime.parse("2022-02-22T20:22:02"),
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index 2679086d..1510f7b 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -29,7 +29,6 @@
 import androidx.health.connect.client.records.CervicalMucusRecord
 import androidx.health.connect.client.records.CervicalMucusRecord.Appearance
 import androidx.health.connect.client.records.CervicalMucusRecord.Sensation
-import androidx.health.connect.client.records.CyclingPedalingCadence
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
@@ -40,7 +39,6 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.ExerciseSessionRecord.ExerciseType
 import androidx.health.connect.client.records.FloorsClimbedRecord
-import androidx.health.connect.client.records.HeartRate
 import androidx.health.connect.client.records.HeartRateRecord
 import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
@@ -70,7 +68,6 @@
 import androidx.health.connect.client.records.SleepStageRecord
 import androidx.health.connect.client.records.SleepStageRecord.StageType
 import androidx.health.connect.client.records.SpeedRecord
-import androidx.health.connect.client.records.StepsCadence
 import androidx.health.connect.client.records.StepsCadenceRecord
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.SwimmingStrokesRecord
@@ -277,11 +274,11 @@
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
                     listOf(
-                        CyclingPedalingCadence(
+                        CyclingPedalingCadenceRecord.Sample(
                             time = START_TIME,
                             revolutionsPerMinute = 1.0,
                         ),
-                        CyclingPedalingCadence(
+                        CyclingPedalingCadenceRecord.Sample(
                             time = START_TIME,
                             revolutionsPerMinute = 2.0,
                         ),
@@ -303,15 +300,15 @@
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
                     listOf(
-                        HeartRate(
+                        HeartRateRecord.Sample(
                             time = START_TIME,
                             beatsPerMinute = 100L,
                         ),
-                        HeartRate(
+                        HeartRateRecord.Sample(
                             time = START_TIME,
                             beatsPerMinute = 110L,
                         ),
-                        HeartRate(
+                        HeartRateRecord.Sample(
                             time = START_TIME,
                             beatsPerMinute = 120L,
                         ),
@@ -637,11 +634,11 @@
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
                     listOf(
-                        StepsCadence(
+                        StepsCadenceRecord.Sample(
                             time = START_TIME,
                             rate = 1.0,
                         ),
-                        StepsCadence(
+                        StepsCadenceRecord.Sample(
                             time = START_TIME,
                             rate = 2.0,
                         ),
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/request/AggregateRequestConverterTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/request/AggregateRequestConverterTest.kt
index 5b30746..b742e18 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/request/AggregateRequestConverterTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/request/AggregateRequestConverterTest.kt
@@ -40,7 +40,7 @@
         .setFieldName("count")
 private val TIME_RANGE_FILTER =
     TimeRangeFilter.between(Instant.ofEpochMilli(123), Instant.ofEpochMilli(456))
-private val DATA_ORIGIN_FILTER = listOf(DataOrigin("testAppName"))
+private val DATA_ORIGIN_FILTER = setOf(DataOrigin("testAppName"))
 
 @RunWith(AndroidJUnit4::class)
 class AggregateRequestConverterTest {
@@ -105,6 +105,6 @@
             )
     }
 
-    private fun List.toProtoList() =
+    private fun Set.toProtoList() =
         this.map { DataProto.DataOrigin.newBuilder().setApplicationId(it.packageName).build() }
 }
diff --git a/health/health-connect-client/src/test/java/androidx/health/platform/client/impl/data/ProtoParcelableTest.kt b/health/health-connect-client/src/test/java/androidx/health/platform/client/impl/data/ProtoParcelableTest.kt
index b295712..dd49758 100644
--- a/health/health-connect-client/src/test/java/androidx/health/platform/client/impl/data/ProtoParcelableTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/platform/client/impl/data/ProtoParcelableTest.kt
@@ -21,6 +21,7 @@
 import androidx.health.platform.client.proto.BytesValue
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -36,6 +37,7 @@
         assertThat(parcelAndRead(protoParcelable).proto).isEqualTo(protoParcelable.proto)
     }
 
+    @Ignore // b/238635208
     @Test
     fun storeInSharedMemory() {
         // Big enough that it will be stored in shared memory.
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
index 0dc453e..1463fc7 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.kt
@@ -19,9 +19,11 @@
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.referentialEqualityPolicy
 import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotMutableState
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
@@ -72,6 +74,60 @@
         assertThat(array).isEqualTo(intArrayOf(1))
     }
 
+    private class CustomStateHolder(
+        initialValue: Int
+    ) {
+        var value by mutableStateOf(initialValue)
+
+        companion object {
+            val Saver: Saver = Saver(
+                save = { it.value },
+                restore = { CustomStateHolder(it) }
+            )
+        }
+    }
+
+    @OptIn(SavedStateHandleSaveableApi::class)
+    @Test
+    fun customStateHolder_simpleRestore() {
+        var stateHolder: CustomStateHolder? = null
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val viewModel = viewModel(activity)
+                stateHolder = viewModel.savedStateHandle.saveable(
+                    key = "key",
+                    saver = CustomStateHolder.Saver
+                ) {
+                    CustomStateHolder(0)
+                }
+            }
+        }
+
+        assertThat(stateHolder?.value).isEqualTo(0)
+
+        activityTestRuleScenario.scenario.onActivity {
+            stateHolder!!.value = 1
+            // we null it to ensure recomposition happened
+            stateHolder = null
+        }
+
+        activityTestRuleScenario.scenario.recreate()
+
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val viewModel = viewModel(activity)
+                stateHolder = viewModel.savedStateHandle.saveable(
+                    key = "key",
+                    saver = CustomStateHolder.Saver
+                ) {
+                    CustomStateHolder(0)
+                }
+            }
+        }
+
+        assertThat(stateHolder?.value).isEqualTo(1)
+    }
+
     private data class CustomState(
         val value: Int
     ) {
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
index 51e909e..8c70d69 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.kt
@@ -62,7 +62,7 @@
     // Hook up saving the state to the SavedStateHandle
     setSavedStateProvider(key) {
         bundleOf("value" to with(saver) {
-            SaverScope { validateValue(value) }.save(value)
+            SaverScope(::validateValue).save(value)
         })
     }
     return value
diff --git a/lifecycle/settings.gradle b/lifecycle/settings.gradle
index 573845c..cda538c 100644
--- a/lifecycle/settings.gradle
+++ b/lifecycle/settings.gradle
@@ -28,7 +28,7 @@
     setupPlayground("..")
     selectProjectsFromAndroidX({ name ->
         if (name.startsWith(":lifecycle")) return true
-        if (name == ":annotation:annotation") return true
+        if (name.startsWith(":annotation")) return true
         if (name == ":internal-testutils-runtime") return true
         if (name == ":internal-testutils-truth") return true
         if (isNeededForComposePlayground(name)) return true
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 713fe38..f9323e2 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.os.Parcel
 import android.os.Parcelable
+import android.util.Log
 import androidx.compose.foundation.lazy.LazyItemScope
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.runtime.Composable
@@ -30,8 +31,11 @@
 import androidx.paging.CombinedLoadStates
 import androidx.paging.DifferCallback
 import androidx.paging.ItemSnapshotList
+import androidx.paging.LOGGER
+import androidx.paging.LOG_TAG
 import androidx.paging.LoadState
 import androidx.paging.LoadStates
+import androidx.paging.Logger
 import androidx.paging.NullPaddedList
 import androidx.paging.PagingData
 import androidx.paging.PagingDataDiffer
@@ -192,6 +196,38 @@
             pagingDataDiffer.collectFrom(it)
         }
     }
+
+    private companion object {
+        init {
+            /**
+             * Implements the Logger interface from paging-common and injects it into the LOGGER
+             * global var stored within Pager.
+             *
+             * Checks for null LOGGER because other runtime entry points to paging can also
+             * inject a Logger
+             */
+            LOGGER = LOGGER ?: object : Logger {
+                override fun isLoggable(level: Int): Boolean {
+                    return Log.isLoggable(LOG_TAG, level)
+                }
+
+                override fun log(level: Int, message: String, tr: Throwable?) {
+                    when {
+                        tr != null && level == Log.DEBUG -> Log.d(LOG_TAG, message, tr)
+                        tr != null && level == Log.VERBOSE -> Log.v(LOG_TAG, message, tr)
+                        level == Log.DEBUG -> Log.d(LOG_TAG, message)
+                        level == Log.VERBOSE -> Log.v(LOG_TAG, message)
+                        else -> {
+                            throw IllegalArgumentException(
+                                "debug level $level is requested but Paging only supports " +
+                                    "default logging for level 2 (DEBUG) or level 3 (VERBOSE)"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 private val IncompleteLoadState = LoadState.NotLoading(false)
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index f6f7ca1..7153d0f 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,7 +25,7 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=8757744
+androidx.playground.snapshotBuildId=8818165
 androidx.playground.metalavaBuildId=8670428
 androidx.playground.dokkaBuildId=7472101
 androidx.studio.type=playground
diff --git a/settings.gradle b/settings.gradle
index 58fa787..06481eb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -91,7 +91,6 @@
         value("androidx.compose.multiplatformEnabled", isMultiplatformEnabled().toString())
         value("androidx.projects", getRequestedProjectSubsetName() ?: "Unset")
         value("androidx.useMaxDepVersions", providers.gradleProperty("androidx.useMaxDepVersions").isPresent().toString())
-        value("androidx.disallowTaskExecution", providers.environmentVariable("DISALLOW_TASK_EXECUTION").isPresent().toString())
 
         publishAlways()
         publishIfAuthenticated()
@@ -951,7 +950,7 @@
 /////////////////////////////
 
 includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN])
+includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN, BuildType.KMP])
 includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.MEDIA, BuildType.WEAR, BuildType.CAMERA])
 includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN])
 includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Tests.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Tests.java
index d1fd14a..1989ab4 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Tests.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Tests.java
@@ -26,6 +26,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.view.ViewConfiguration;
 
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Direction;
@@ -59,23 +60,73 @@
     public void testClick() {
         launchTestActivity(UiObject2TestClickActivity.class);
 
-        // Find the button and verify its initial state
-        UiObject2 button = mDevice.findObject(By.res(TEST_APP, "button"));
-        assertEquals("Click Me!", button.getText());
+        // Short click with no parameter (`click()`).
+        UiObject2 button1 = mDevice.findObject(By.res(TEST_APP, "button1"));
+        assertEquals("text1", button1.getText());
+        button1.click();
+        button1.wait(Until.textEquals("text1_clicked"), TIMEOUT_MS);
+        assertEquals("text1_clicked", button1.getText());
+    }
 
-        // Click on the button and verify that the text has changed
-        button.click();
-        button.wait(Until.textEquals("I've been clicked!"), TIMEOUT_MS);
-        assertEquals("I've been clicked!", button.getText());
+    @Test
+    public void testClick_point() {
+        launchTestActivity(UiObject2TestClickActivity.class);
 
-        // Find the checkbox and verify its initial state
-        UiObject2 checkbox = mDevice.findObject(By.res(TEST_APP, "check_box"));
-        assertFalse(checkbox.isChecked());
+        // Short click with a point position as a parameter (`click(Point point)`).
 
-        // Click on the checkbox and verify that it is now checked
-        checkbox.click();
-        checkbox.wait(Until.checked(true), TIMEOUT_MS);
-        assertTrue(checkbox.isChecked());
+        // Point inside the button.
+        UiObject2 button2 = mDevice.findObject(By.res(TEST_APP, "button2"));
+        assertEquals("text2", button2.getText());
+        button2.click(getPointInsideBounds(button2));
+        button2.wait(Until.textEquals("text2_clicked"), TIMEOUT_MS);
+        assertEquals("text2_clicked", button2.getText());
+
+        // Point outside the button.
+        UiObject2 button3 = mDevice.findObject(By.res(TEST_APP, "button3"));
+        assertEquals("text3", button3.getText());
+        button3.click(getPointOutsideBounds(button3));
+        button3.wait(Until.textEquals("text3_clicked"), TIMEOUT_MS);
+        assertEquals("text3_clicked", button3.getText());
+    }
+
+    @Test
+    public void testClick_duration() {
+        launchTestActivity(UiObject2TestClickActivity.class);
+
+        // Short click with a time duration as a parameter (`click(long duration)`).
+        UiObject2 button4 = mDevice.findObject(By.res(TEST_APP, "button4"));
+        assertEquals("text4", button4.getText());
+        button4.click((long) (ViewConfiguration.getLongPressTimeout() / 1.5));
+        button4.wait(Until.textEquals("text4_clicked"), TIMEOUT_MS);
+        assertEquals("text4_clicked", button4.getText());
+
+        // Long click with a time duration as a parameter (`click(long duration)`).
+        UiObject2 button5 = mDevice.findObject(By.res(TEST_APP, "button5"));
+        assertEquals("text5", button5.getText());
+        button5.click((long) (ViewConfiguration.getLongPressTimeout() * 1.5));
+        button5.wait(Until.textEquals("text5_long_clicked"), TIMEOUT_MS);
+        assertEquals("text5_long_clicked", button5.getText());
+    }
+
+    @Test
+    public void testClick_pointAndDuration() {
+        launchTestActivity(UiObject2TestClickActivity.class);
+
+        // Short click with two parameters (`click(Point point, long duration)`).
+        UiObject2 button6 = mDevice.findObject(By.res(TEST_APP, "button6"));
+        assertEquals("text6", button6.getText());
+        button6.click(getPointInsideBounds(button6),
+                (long) (ViewConfiguration.getLongPressTimeout() / 1.5));
+        button6.wait(Until.textEquals("text6_clicked"), TIMEOUT_MS);
+        assertEquals("text6_clicked", button6.getText());
+
+        // Long click with two parameters (`click(Point point, long duration)`).
+        UiObject2 button7 = mDevice.findObject(By.res(TEST_APP, "button7"));
+        assertEquals("text7", button7.getText());
+        button7.click(getPointInsideBounds(button7),
+                (long) (ViewConfiguration.getLongPressTimeout() * 1.5));
+        button7.wait(Until.textEquals("text7_long_clicked"), TIMEOUT_MS);
+        assertEquals("text7_long_clicked", button7.getText());
     }
 
     @Test
@@ -288,8 +339,8 @@
         UiObject2 checkBox = mDevice.findObject(By.res(TEST_APP, "check_box"));
         assertTrue(checkBox.isCheckable());
         // Button objects are not checkable by default.
-        UiObject2 button = mDevice.findObject(By.res(TEST_APP, "button"));
-        assertFalse(button.isCheckable());
+        UiObject2 button1 = mDevice.findObject(By.res(TEST_APP, "button1"));
+        assertFalse(button1.isCheckable());
     }
 
     @Test
@@ -404,7 +455,7 @@
         scaleText.wait(Until.textNotEquals("1.0f"), TIMEOUT_MS);
         float scaleValueAfterPinch = Float.valueOf(scaleText.getText());
         assertTrue(String.format("Expected scale value to be less than 1f after pinchClose(), "
-                        + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch < 1f);
+                + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch < 1f);
     }
 
     @Test
@@ -578,4 +629,20 @@
 
     public void testWaitForGone() {}
     */
+
+    /* Helper method for `testClick*()`. Get a point inside the object. */
+    private Point getPointInsideBounds(UiObject2 obj) {
+        Rect objBounds = obj.getVisibleBounds();
+        int pointX = objBounds.left + objBounds.width() / 3;
+        int pointY = objBounds.top + objBounds.height() / 3;
+        return new Point(pointX, pointY);
+    }
+
+    /* Helper method for `testClick*()`. Get a point outside the object. */
+    private Point getPointOutsideBounds(UiObject2 obj) {
+        Rect objBounds = obj.getVisibleBounds();
+        int pointX = objBounds.right + objBounds.width() / 3;
+        int pointY = objBounds.bottom + objBounds.height() / 3;
+        return new Point(pointX, pointY);
+    }
 }
diff --git a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/UiObject2TestClickActivity.java b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/UiObject2TestClickActivity.java
index ec0e5e1..35e4738 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/UiObject2TestClickActivity.java
+++ b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/UiObject2TestClickActivity.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
+import android.view.View.OnLongClickListener;
 import android.widget.Button;
 
 import androidx.annotation.NonNull;
@@ -31,9 +32,28 @@
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.uiobject2_testclick_activity);
+
+        // Set up the long-clickable buttons.
+        Button button4 = (Button) findViewById(R.id.button4);
+        Button button5 = (Button) findViewById(R.id.button5);
+        Button button6 = (Button) findViewById(R.id.button6);
+        Button button7 = (Button) findViewById(R.id.button7);
+
+        button4.setOnLongClickListener(new OnButtonLongClick());
+        button5.setOnLongClickListener(new OnButtonLongClick());
+        button6.setOnLongClickListener(new OnButtonLongClick());
+        button7.setOnLongClickListener(new OnButtonLongClick());
     }
 
     public void onButtonClick(@NonNull View v) {
-        ((Button)v).setText("I've been clicked!");
+        ((Button) v).append("_clicked");
+    }
+
+    static class OnButtonLongClick implements OnLongClickListener {
+        @Override
+        public boolean onLongClick(View v) {
+            ((Button) v).append("_long_clicked");
+            return true;
+        }
     }
 }
diff --git a/test/uiautomator/integration-tests/testapp/src/main/res/layout/byselector_test_activity.xml b/test/uiautomator/integration-tests/testapp/src/main/res/layout/byselector_test_activity.xml
index deb6cc2..f983ce0 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/res/layout/byselector_test_activity.xml
+++ b/test/uiautomator/integration-tests/testapp/src/main/res/layout/byselector_test_activity.xml
@@ -253,4 +253,9 @@
             android:text="not_selected" />
     
 
+    
+
 
diff --git a/test/uiautomator/integration-tests/testapp/src/main/res/layout/uiobject2_testclick_activity.xml b/test/uiautomator/integration-tests/testapp/src/main/res/layout/uiobject2_testclick_activity.xml
index c725b6f..54a4ccd 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/res/layout/uiobject2_testclick_activity.xml
+++ b/test/uiautomator/integration-tests/testapp/src/main/res/layout/uiobject2_testclick_activity.xml
@@ -20,17 +20,72 @@
     android:orientation="vertical"
     tools:context=".UiObject2TestClickActivity">
 
+    
     
-        android:id="@+id/button"
+        android:id="@+id/button1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:onClick="onButtonClick"
-        android:text="Click Me!" />
+        android:text="text1" />
 
+    
+    
+        android:id="@+id/button2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="onButtonClick"
+        android:text="text2" />
+
+    
+    
+        android:id="@+id/button3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="onButtonClick"
+        android:text="text3" />
+
+    
+    
+        android:id="@+id/button4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:longClickable="true"
+        android:onClick="onButtonClick"
+        android:text="text4" />
+
+    
+    
+        android:id="@+id/button5"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:longClickable="true"
+        android:onClick="onButtonClick"
+        android:text="text5" />
+
+    
+    
+        android:id="@+id/button6"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:longClickable="true"
+        android:onClick="onButtonClick"
+        android:text="text6" />
+
+    
+    
+        android:id="@+id/button7"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:longClickable="true"
+        android:onClick="onButtonClick"
+        android:text="text7" />
+
+    
     
         android:id="@+id/check_box"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:text="check_box"
         android:checked="false" />
 
 
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index 6582467..0888962 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -18,11 +18,32 @@
 
 plugins {
     id("AndroidXPlugin")
-    id("kotlin")
 }
+androidXMultiplatform {
+    jvm {}
+    macosX64()
+    linuxX64()
+    macosArm64()
 
-dependencies {
-    implementation(libs.kotlinStdlib)
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(project(":datastore:datastore-core"))
+                api(project(":datastore:datastore-core-okio"))
+                api(libs.kotlinStdlibCommon)
+                api(libs.kotlinTestCommon)
+                api(libs.kotlinCoroutinesCore)
+                api(libs.okio)
+            }
+        }
+
+        jvmMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+                api(libs.kotlinTest)
+            }
+        }
+    }
 }
 
 androidx {
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
similarity index 97%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
index 3244e85..afde3af 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.Storage
 import androidx.datastore.core.okio.OkioStorage
 import kotlin.random.Random
 import kotlin.reflect.KClass
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
similarity index 86%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
index 94de2b6..772f901 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.Storage
+import androidx.datastore.core.DataStoreFactory.create
 import kotlin.reflect.KClass
 import kotlinx.coroutines.CoroutineScope
 
-// TODO(b/237677833): move this class when datastore test utils is created
 abstract class TestIO(
     protected val dirName: String = "datastore-test-dir"
 ) {
@@ -29,7 +31,7 @@
         scope: CoroutineScope,
         futureFile: () -> TestFile
     ): DataStore {
-        return SingleProcessDataStore(getStorage(serializerConfig, futureFile), scope = scope)
+        return create(getStorage(serializerConfig, futureFile), scope = scope)
     }
 
     abstract fun getStorage(
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
similarity index 91%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
index 82885ac..cdfff76 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.CorruptionException
 import androidx.datastore.core.okio.OkioSerializer
 import okio.BufferedSink
 import okio.BufferedSource
@@ -23,8 +24,7 @@
 import okio.IOException
 import okio.use
 
-// TODO(b/237677833): remove this class when datastore test utils is created
-internal class TestingOkioSerializer(
+class TestingOkioSerializer(
     val config: TestingSerializerConfig
 ) : OkioSerializer {
 
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
similarity index 96%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
index 3bd6754..2ea35c5 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
 import kotlin.jvm.Volatile
 
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
similarity index 88%
rename from datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt
rename to testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
index 00ca26b6..3a2c5d0 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt
+++ b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.FileStorage
+import androidx.datastore.core.Storage
+import androidx.datastore.core.TestingSerializer
 import java.io.File
 import java.io.IOException
 import kotlin.reflect.KClass
@@ -43,9 +46,9 @@
         serializerConfig: TestingSerializerConfig,
         futureFile: () -> TestFile
     ): Storage {
-        return FileStorage(
-            TestingSerializer(serializerConfig)
-        ) { (futureFile() as JavaIOFile).file }
+        return FileStorage(TestingSerializer(serializerConfig)) {
+            (futureFile() as JavaIOFile).file
+        }
     }
 
     override fun isDirectory(file: JavaIOFile): Boolean {
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
similarity index 95%
rename from datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt
rename to testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
index 9069e0c..fcfc44b 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt
+++ b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
@@ -16,11 +16,12 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import java.io.IOException
 import java.io.InputStream
 import java.io.OutputStream
 
-internal class TestingSerializer(
+class TestingSerializer(
     val config: TestingSerializerConfig = TestingSerializerConfig(),
 ) : Serializer {
     override suspend fun readFrom(input: InputStream): Byte {
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/Button.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/Button.java
index 14cc781..81f4392 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/Button.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/Button.java
@@ -169,8 +169,8 @@
         /**
          * Sets the size for the {@link Button}. Strongly recommended values are {@link
          * ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and {@link
-         * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link
-         * ButtonDefaults#DEFAULT_SIZE} will be used.
+         * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link ButtonDefaults#DEFAULT_SIZE} will be
+         * used.
          */
         @NonNull
         public Builder setSize(@NonNull DpProp size) {
@@ -181,8 +181,8 @@
         /**
          * Sets the size for the {@link Button}. Strongly recommended values are {@link
          * ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and {@link
-         * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link
-         * ButtonDefaults#DEFAULT_SIZE} will be used.
+         * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link ButtonDefaults#DEFAULT_SIZE} will be
+         * used.
          */
         @NonNull
         public Builder setSize(@Dimension(unit = DP) float size) {
@@ -242,12 +242,11 @@
 
         /**
          * Sets the content of this Button to be the given text with the default font for the set
-         * size (for the {@link ButtonDefaults#DEFAULT_SIZE}, {@link
-         * ButtonDefaults#LARGE_SIZE} and {@link ButtonDefaults#EXTRA_LARGE_SIZE} is
-         * {@link Typography#TYPOGRAPHY_TITLE2}, {@link Typography#TYPOGRAPHY_TITLE1} and {@link
-         * Typography#TYPOGRAPHY_DISPLAY3} respectively). Any previously added content will be
-         * overridden. Text should contain no more than 3 characters, otherwise it will overflow
-         * from the edges.
+         * size (for the {@link ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and
+         * {@link ButtonDefaults#EXTRA_LARGE_SIZE} is {@link Typography#TYPOGRAPHY_TITLE2}, {@link
+         * Typography#TYPOGRAPHY_TITLE1} and {@link Typography#TYPOGRAPHY_DISPLAY3} respectively).
+         * Any previously added content will be overridden. Text should contain no more than 3
+         * characters, otherwise it will overflow from the edges.
          */
         @NonNull
         public Builder setTextContent(@NonNull String text) {
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
index b540215..df5d7d0 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
@@ -45,13 +45,25 @@
      * The default percentage for the top margin for primary chip in the {@link PrimaryLayout} on
      * square devices.
      */
-    static final float PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT = 15.6f / 100;
+    static final float PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT = 13.3f / 100;
+
+    /**
+     * The default spacer above primary label in {@link PrimaryLayout} to make space for Tile icon
+     * on round devices.
+     */
+    static final DpProp PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP = dp(0);
+
+    /**
+     * The default spacer above primary label in {@link PrimaryLayout} to make space for Tile icon
+     * on square devices.
+     */
+    static final DpProp PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP = dp(4);
 
     /**
      * The default percentage for the horizontal margin for primary chip in the {@link
      * PrimaryLayout}.
      */
-    static final float PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT = 5.2f / 100;
+    static final float PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT = 6.3f / 100;
 
     /**
      * The default percentage for the horizontal margin for primary chip in the {@link
@@ -63,7 +75,7 @@
      * The padding for the primary chip in {@link PrimaryLayout} so it doesn't bleed off screen if
      * text is too big.
      */
-    static final float PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP = 32;
+    static final float PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP = 30;
 
     /**
      * The padding for the primary chip in {@link PrimaryLayout} so it doesn't bleed off screen if
@@ -113,7 +125,7 @@
     static final DpProp MULTI_BUTTON_1_SIZE = ButtonDefaults.EXTRA_LARGE_SIZE;
 
     /** The default width for vertical spacer between buttons in the {@link MultiButtonLayout}. */
-    static final DpProp MULTI_BUTTON_SPACER_WIDTH = dp(8);
+    static final DpProp MULTI_BUTTON_SPACER_WIDTH = dp(6);
 
     /**
      * The default height for horizontal spacer between buttons in the {@link MultiButtonLayout}.
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
index 228e91a..ce84643 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
@@ -35,6 +35,8 @@
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT;
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT;
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT;
+import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP;
+import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP;
 
 import android.annotation.SuppressLint;
 
@@ -128,6 +130,13 @@
      */
     static final int CONTENT_PRESENT = 0x8;
 
+    /** Position of the primary label in inner column if exists. */
+    static final int PRIMARY_LABEL_POSITION = 1;
+    /** Position of the content in inner column element when primary label is present. */
+    static final int CONTENT_POSITION = 3;
+    /** Position of the content in inner column element when there primary label is not present. */
+    static final int CONTENT_ONLY_POSITION = 0;
+
     /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Retention(RetentionPolicy.SOURCE)
@@ -250,6 +259,8 @@
                             .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER);
 
             if (mPrimaryLabelText != null) {
+                innerContentBuilder.addContent(
+                        new Spacer.Builder().setHeight(getPrimaryLabelTopSpacerHeight()).build());
                 innerContentBuilder.addContent(mPrimaryLabelText);
                 innerContentBuilder.addContent(
                         new Spacer.Builder().setHeight(mVerticalSpacerHeight).build());
@@ -372,6 +383,14 @@
                     ? PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP
                     : PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_SQUARE_DP;
         }
+
+        /** Returns the spacer height to be placed above primary label to accommodate Tile icon. */
+        @NonNull
+        private DpProp getPrimaryLabelTopSpacerHeight() {
+            return isRoundDevice(mDeviceParameters)
+                    ? PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP
+                    : PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP;
+        }
     }
 
     /** Get the primary label content from this layout. */
@@ -380,8 +399,7 @@
         if (!areElementsPresent(PRIMARY_LABEL_PRESENT)) {
             return null;
         }
-        // By tag we know that primary label exists. It will always be at position 0.
-        return mInnerColumn.get(0);
+        return mInnerColumn.get(PRIMARY_LABEL_POSITION);
     }
 
     /** Get the secondary label content from this layout. */
@@ -400,9 +418,9 @@
         if (!areElementsPresent(CONTENT_PRESENT)) {
             return null;
         }
-        // By tag we know that content exists. It will be at position 0 if there is no primary
-        // label, or at position 2 (primary label, spacer - content) otherwise.
-        int contentPosition = areElementsPresent(PRIMARY_LABEL_PRESENT) ? 2 : 0;
+        int contentPosition =
+                areElementsPresent(PRIMARY_LABEL_PRESENT)
+                        ? CONTENT_POSITION : CONTENT_ONLY_POSITION;
         return ((Box) mInnerColumn.get(contentPosition)).getContents().get(0);
     }
 
@@ -421,13 +439,13 @@
     @SuppressLint("ResourceType")
     @Dimension(unit = DP)
     public float getVerticalSpacerHeight() {
-        // We don't need special cases for primary or secondary label - if primary label is present,
-        // then the first spacer is at the position 1 and we can get height from it. However, if the
-        // primary label is not present, the spacer will be between content and secondary label (if
-        // there is secondary label) so its position is again 1.
         if (areElementsPresent(PRIMARY_LABEL_PRESENT)
                 || areElementsPresent(SECONDARY_LABEL_PRESENT)) {
-            LayoutElement element = mInnerColumn.get(1);
+            LayoutElement element =
+                    mInnerColumn.get(
+                            (areElementsPresent(PRIMARY_LABEL_PRESENT)
+                                    ? PRIMARY_LABEL_POSITION : CONTENT_ONLY_POSITION) + 1
+                    );
             if (element instanceof Spacer) {
                 SpacerDimension height = ((Spacer) element).getHeight();
                 if (height instanceof DpProp) {
diff --git a/wear/tiles/tiles-material/src/test/java/androidx/wear/tiles/material/layouts/PrimaryLayoutTest.java b/wear/tiles/tiles-material/src/test/java/androidx/wear/tiles/material/layouts/PrimaryLayoutTest.java
index e6d34e5..1f749e6 100644
--- a/wear/tiles/tiles-material/src/test/java/androidx/wear/tiles/material/layouts/PrimaryLayoutTest.java
+++ b/wear/tiles/tiles-material/src/test/java/androidx/wear/tiles/material/layouts/PrimaryLayoutTest.java
@@ -153,13 +153,13 @@
     public void testWrongTag() {
         Box box =
                 new Box.Builder()
-                    .setModifiers(
-                        new Modifiers.Builder()
-                            .setMetadata(
-                                new ElementMetadata.Builder()
-                                    .setTagData("test".getBytes(UTF_8))
-                                    .build())
-                                .build())
+                        .setModifiers(
+                                new Modifiers.Builder()
+                                        .setMetadata(
+                                                new ElementMetadata.Builder()
+                                                        .setTagData("test".getBytes(UTF_8))
+                                                        .build())
+                                        .build())
                         .build();
 
         assertThat(PrimaryLayout.fromLayoutElement(box)).isNull();
@@ -169,15 +169,15 @@
     public void testWrongLengthTag() {
         Box box =
                 new Box.Builder()
-                    .setModifiers(
-                        new Modifiers.Builder()
-                            .setMetadata(
-                                new ElementMetadata.Builder()
-                                    .setTagData(
-                                        PrimaryLayout.METADATA_TAG_PREFIX
-                                            .getBytes(UTF_8))
-                                    .build())
-                            .build())
+                        .setModifiers(
+                                new Modifiers.Builder()
+                                        .setMetadata(
+                                                new ElementMetadata.Builder()
+                                                        .setTagData(
+                                                                PrimaryLayout.METADATA_TAG_PREFIX
+                                                                        .getBytes(UTF_8))
+                                                        .build())
+                                        .build())
                         .build();
 
         assertThat(PrimaryLayout.fromLayoutElement(box)).isNull();
@@ -227,18 +227,22 @@
             assertThat(actualLayout.getContent()).isNull();
         } else {
             assertThat(actualLayout.getContent().toLayoutElementProto())
-                .isEqualTo(expectedContent.toLayoutElementProto());
+                    .isEqualTo(expectedContent.toLayoutElementProto());
             expectedMetadata[PrimaryLayout.FLAG_INDEX] =
-                (byte) (expectedMetadata[PrimaryLayout.FLAG_INDEX] | PrimaryLayout.CONTENT_PRESENT);
+                    (byte)
+                            (expectedMetadata[PrimaryLayout.FLAG_INDEX]
+                                    | PrimaryLayout.CONTENT_PRESENT);
         }
 
         if (expectedPrimaryChip == null) {
             assertThat(actualLayout.getPrimaryChipContent()).isNull();
         } else {
             assertThat(actualLayout.getPrimaryChipContent().toLayoutElementProto())
-                .isEqualTo(expectedPrimaryChip.toLayoutElementProto());
+                    .isEqualTo(expectedPrimaryChip.toLayoutElementProto());
             expectedMetadata[PrimaryLayout.FLAG_INDEX] =
-                (byte) (expectedMetadata[PrimaryLayout.FLAG_INDEX] | PrimaryLayout.CHIP_PRESENT);
+                    (byte)
+                            (expectedMetadata[PrimaryLayout.FLAG_INDEX]
+                                    | PrimaryLayout.CHIP_PRESENT);
         }
 
         assertThat(actualLayout.getVerticalSpacerHeight()).isEqualTo(height);
@@ -247,22 +251,22 @@
             assertThat(actualLayout.getPrimaryLabelTextContent()).isNull();
         } else {
             assertThat(actualLayout.getPrimaryLabelTextContent().toLayoutElementProto())
-                .isEqualTo(expectedPrimaryLabel.toLayoutElementProto());
+                    .isEqualTo(expectedPrimaryLabel.toLayoutElementProto());
             expectedMetadata[PrimaryLayout.FLAG_INDEX] =
-                (byte)
-                    (expectedMetadata[PrimaryLayout.FLAG_INDEX]
-                        | PrimaryLayout.PRIMARY_LABEL_PRESENT);
+                    (byte)
+                            (expectedMetadata[PrimaryLayout.FLAG_INDEX]
+                                    | PrimaryLayout.PRIMARY_LABEL_PRESENT);
         }
 
         if (expectedSecondaryLabel == null) {
             assertThat(actualLayout.getSecondaryLabelTextContent()).isNull();
         } else {
             assertThat(actualLayout.getSecondaryLabelTextContent().toLayoutElementProto())
-                .isEqualTo(expectedSecondaryLabel.toLayoutElementProto());
+                    .isEqualTo(expectedSecondaryLabel.toLayoutElementProto());
             expectedMetadata[PrimaryLayout.FLAG_INDEX] =
-                (byte)
-                    (expectedMetadata[PrimaryLayout.FLAG_INDEX]
-                        | PrimaryLayout.SECONDARY_LABEL_PRESENT);
+                    (byte)
+                            (expectedMetadata[PrimaryLayout.FLAG_INDEX]
+                                    | PrimaryLayout.SECONDARY_LABEL_PRESENT);
         }
 
         assertThat(actualLayout.getMetadataTag()).isEqualTo(expectedMetadata);
diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt
index 8efb24f..aca452d 100644
--- a/wear/watchface/watchface-client/api/current.txt
+++ b/wear/watchface/watchface-client/api/current.txt
@@ -20,12 +20,10 @@
   }
 
   public final class ComplicationSlotState {
-    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId, android.graphics.Rect boundsWithMargins);
-    ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
+    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
     ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, androidx.wear.watchface.complications.data.ComplicationType defaultDataSourceType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras);
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
-    method public android.graphics.Rect getBoundsWithMargins();
     method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.watchface.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy getDefaultDataSourcePolicy();
@@ -38,7 +36,6 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
-    property public final android.graphics.Rect boundsWithMargins;
     property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.watchface.complications.data.ComplicationType currentType;
     property public final androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy;
diff --git a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
index 90066f3..3697506 100644
--- a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
@@ -22,14 +22,12 @@
   }
 
   public final class ComplicationSlotState {
-    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId, android.graphics.Rect boundsWithMargins);
-    ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
-    ctor @androidx.wear.watchface.complications.data.ComplicationExperimental public ComplicationSlotState(android.graphics.Rect bounds, android.graphics.Rect boundsWithMargins, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId, androidx.wear.watchface.BoundingArc? edgeComplicationBoundingArc);
+    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
+    ctor @androidx.wear.watchface.complications.data.ComplicationExperimental public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId, androidx.wear.watchface.BoundingArc? edgeComplicationBoundingArc);
     ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, androidx.wear.watchface.complications.data.ComplicationType defaultDataSourceType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras);
     method @androidx.wear.watchface.complications.data.ComplicationExperimental public androidx.wear.watchface.BoundingArc? getBoundingArc();
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
-    method public android.graphics.Rect getBoundsWithMargins();
     method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.watchface.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy getDefaultDataSourcePolicy();
@@ -42,7 +40,6 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
-    property public final android.graphics.Rect boundsWithMargins;
     property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.watchface.complications.data.ComplicationType currentType;
     property public final androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy;
diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt
index 8efb24f..aca452d 100644
--- a/wear/watchface/watchface-client/api/restricted_current.txt
+++ b/wear/watchface/watchface-client/api/restricted_current.txt
@@ -20,12 +20,10 @@
   }
 
   public final class ComplicationSlotState {
-    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId, android.graphics.Rect boundsWithMargins);
-    ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
+    ctor public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras, Integer? nameResourceId, Integer? screenReaderNameResourceId);
     ctor @Deprecated public ComplicationSlotState(android.graphics.Rect bounds, int boundsType, java.util.List supportedTypes, androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy, androidx.wear.watchface.complications.data.ComplicationType defaultDataSourceType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.watchface.complications.data.ComplicationType currentType, boolean fixedComplicationDataSource, android.os.Bundle complicationConfigExtras);
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
-    method public android.graphics.Rect getBoundsWithMargins();
     method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.watchface.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy getDefaultDataSourcePolicy();
@@ -38,7 +36,6 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
-    property public final android.graphics.Rect boundsWithMargins;
     property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.watchface.complications.data.ComplicationType currentType;
     property public final androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy defaultDataSourcePolicy;
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationSlotState.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationSlotState.kt
index de193bd..77a1fcb 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationSlotState.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationSlotState.kt
@@ -35,12 +35,6 @@
     /** Screen space bounds of the [ComplicationSlot] in pixels. */
     public val bounds: Rect
 
-    /**
-     * Screen space bounds of the [ComplicationSlot] in pixels including any specified margins. This
-     * is useful for tap detection (especially for small complications) and doesn't affect rendering.
-     */
-    public val boundsWithMargins: Rect
-
     /** The type of the complication's bounds. */
     @ComplicationSlotBoundsType
     public val boundsType: Int
@@ -123,8 +117,6 @@
      * the complication slot in an editor.
      * @param screenReaderNameResourceId The ID of a string resource (or `null` if absent) to
      * identify the complication slot in a screen reader.
-     * @param boundsWithMargins Screen space bounds of the [ComplicationSlot] in pixels including
-     * any specified margins.
      */
     public constructor(
         bounds: Rect,
@@ -139,72 +131,9 @@
         @Suppress("AutoBoxing")
         nameResourceId: Int?,
         @Suppress("AutoBoxing")
-        screenReaderNameResourceId: Int?,
-        boundsWithMargins: Rect
-    ) {
-        this.bounds = bounds
-        this.boundsWithMargins = boundsWithMargins
-        this.boundsType = boundsType
-        this.supportedTypes = supportedTypes
-        this.defaultDataSourcePolicy = defaultDataSourcePolicy
-        this.isEnabled = isEnabled
-        this.isInitiallyEnabled = isInitiallyEnabled
-        this.currentType = currentType
-        this.fixedComplicationDataSource = fixedComplicationDataSource
-        this.complicationConfigExtras = complicationConfigExtras
-        this.nameResourceId = nameResourceId
-        this.screenReaderNameResourceId = screenReaderNameResourceId
-
-        @OptIn(ComplicationExperimental::class)
-        this.boundingArc = null
-    }
-
-    /**
-     * @param bounds Screen space bounds of the [ComplicationSlot] in pixels.
-     * @param boundsType The type of the complication's bounds.
-     * @param supportedTypes The [ComplicationType]s supported by this complication.
-     * @param defaultDataSourcePolicy The [DefaultComplicationDataSourcePolicy] for this
-     * complication slot.
-     * @param isEnabled Whether or not the complication is currently enabled (i.e. it should be
-     * drawn).
-     * @param isInitiallyEnabled Whether or not the complication was initially enabled before
-     * considering any [ComplicationSlotsOption] whose [ComplicationSlotOverlay]s may enable or
-     * disable complicationSlots.
-     * @param currentType The [ComplicationType] of the complication's current [ComplicationData].
-     * @param fixedComplicationDataSource Whether or not the complication data source is fixed (i.e
-     * the user can't configure it).
-     * @param complicationConfigExtras Extras to be merged into the Intent sent when invoking the
-     * complication data source chooser activity.
-     * @param nameResourceId The ID of a string resource (or `null` if absent) to visually identify
-     * the complication slot in an editor.
-     * @param screenReaderNameResourceId The ID of a string resource (or `null` if absent) to
-     * identify the complication slot in a screen reader.
-     */
-    @Deprecated(
-        "Constructor without boundsWithMargins is deprecated",
-        ReplaceWith(
-            "ComplicationSlotState(Rect, Int, List, " +
-                "DefaultComplicationDataSourcePolicy, Boolean, Boolean, ComplicationType, " +
-                "Boolean, Bundle, Int?, Int?, Rect?)"
-        )
-    )
-    public constructor(
-        bounds: Rect,
-        @ComplicationSlotBoundsType boundsType: Int,
-        supportedTypes: List,
-        defaultDataSourcePolicy: DefaultComplicationDataSourcePolicy,
-        isEnabled: Boolean,
-        isInitiallyEnabled: Boolean,
-        currentType: ComplicationType,
-        fixedComplicationDataSource: Boolean,
-        complicationConfigExtras: Bundle,
-        @Suppress("AutoBoxing")
-        nameResourceId: Int?,
-        @Suppress("AutoBoxing")
         screenReaderNameResourceId: Int?
     ) {
         this.bounds = bounds
-        this.boundsWithMargins = bounds
         this.boundsType = boundsType
         this.supportedTypes = supportedTypes
         this.defaultDataSourcePolicy = defaultDataSourcePolicy
@@ -222,8 +151,6 @@
 
     /**
      * @param bounds Screen space bounds of the [ComplicationSlot] in pixels.
-     * @param boundsWithMargins Screen space bounds of the [ComplicationSlot] in pixels including
-     * any specified margins.
      * @param boundsType The type of the complication's bounds.
      * @param supportedTypes The [ComplicationType]s supported by this complication.
      * @param defaultDataSourcePolicy The [DefaultComplicationDataSourcePolicy] for this
@@ -248,7 +175,6 @@
     @ComplicationExperimental
     public constructor(
         bounds: Rect,
-        boundsWithMargins: Rect,
         @ComplicationSlotBoundsType boundsType: Int,
         supportedTypes: List,
         defaultDataSourcePolicy: DefaultComplicationDataSourcePolicy,
@@ -264,7 +190,6 @@
         edgeComplicationBoundingArc: BoundingArc?
     ) {
         this.bounds = bounds
-        this.boundsWithMargins = boundsWithMargins
         this.boundsType = boundsType
         this.supportedTypes = supportedTypes
         this.defaultDataSourcePolicy = defaultDataSourcePolicy
@@ -316,7 +241,6 @@
         complicationConfigExtras: Bundle
     ) {
         this.bounds = bounds
-        this.boundsWithMargins = bounds
         this.boundsType = boundsType
         this.supportedTypes = supportedTypes
         this.defaultDataSourcePolicy = when {
@@ -364,7 +288,6 @@
         complicationStateWireFormat: ComplicationStateWireFormat
     ) : this(
         complicationStateWireFormat.bounds,
-        complicationStateWireFormat.boundsWithMargins ?: Rect(),
         complicationStateWireFormat.boundsType,
         complicationStateWireFormat.supportedTypes.map { ComplicationType.fromWireType(it) },
         DefaultComplicationDataSourcePolicy(
@@ -394,7 +317,7 @@
     @Suppress("Deprecation")
     override fun toString(): String {
         @OptIn(ComplicationExperimental::class)
-        return "ComplicationSlotState(bounds=$bounds, boundsWithMargins=$boundsWithMargins, " +
+        return "ComplicationSlotState(bounds=$bounds, " +
             "boundsType=$boundsType, supportedTypes=$supportedTypes, " +
             "defaultDataSourcePolicy=$defaultDataSourcePolicy, " +
             "defaultDataSourceType=$defaultDataSourceType, isEnabled=$isEnabled, " +
@@ -414,7 +337,6 @@
         other as ComplicationSlotState
 
         if (bounds != other.bounds) return false
-        if (boundsWithMargins != other.boundsWithMargins) return false
         if (boundsType != other.boundsType) return false
         if (supportedTypes != other.supportedTypes) return false
         if (defaultDataSourcePolicy != other.defaultDataSourcePolicy) return false
@@ -435,7 +357,6 @@
     @Suppress("Deprecation")
     override fun hashCode(): Int {
         var result = bounds.hashCode()
-        result = 31 * result + boundsWithMargins.hashCode()
         result = 31 * result + boundsType
         result = 31 * result + supportedTypes.hashCode()
         result = 31 * result + defaultDataSourcePolicy.hashCode()
diff --git a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
index 34f094d..88efeb2 100644
--- a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
+++ b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
@@ -64,6 +64,7 @@
 import com.google.common.truth.Truth;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -1101,6 +1102,7 @@
         assertThat(output).isEqualTo(Color.WHITE);
     }
 
+    @Ignore // b/238635208
     @Test
     public void placeholderIcon() {
         mComplicationRenderer.setComplicationData(
@@ -1112,6 +1114,7 @@
         assertThat(mComplicationRenderer.mIsPlaceholderIcon).isTrue();
     }
 
+    @Ignore // b/238635208
     @Test
     public void placeholderSmallImage() {
         mComplicationRenderer.setComplicationData(
@@ -1123,6 +1126,7 @@
         assertThat(mComplicationRenderer.mIsPlaceholderSmallImage).isTrue();
     }
 
+    @Ignore // b/238635208
     @Test
     public void placeholderPhotoImage() {
         mComplicationRenderer.setComplicationData(
diff --git a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index 2e81b76..e20b6f8 100644
--- a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -809,7 +809,6 @@
             }
             ComplicationSlotState(
                 it.value.computeBounds(editorDelegate.screenBounds, type, applyMargins = false),
-                it.value.computeBounds(editorDelegate.screenBounds, type, applyMargins = true),
                 it.value.boundsType,
                 it.value.supportedTypes,
                 it.value.defaultDataSourcePolicy,
diff --git a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
index be20dbb..0d1a4e8 100644
--- a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
+++ b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
@@ -29,6 +29,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.fail
 import org.junit.Assert.assertThrows
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -900,6 +901,7 @@
         assertThat(digestHash1).isNotEqualTo(digestHash2)
     }
 
+    @Ignore // b/238635208
     @Test
     public fun digestHashSensitiveToIconChanges() {
         val colorStyleSetting1 = ListUserStyleSetting(
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java
index 97301c4..07844a8 100644
--- a/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java
+++ b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java
@@ -209,6 +209,22 @@
      *       standard upright position.
      * 
      *
+     * 

Additionally, a location zone will be provided for the button, which will be one of the + * {@code LOCATION_*} constants. This defines the general area of the button on the device, and + * can be passed to {@link #getButtonLabel} to provide a human-understandable name for the + * location. There are two sets of locations for a device, depending on whether it is a circular + * or rectilinear device. + * + *

The "compass" locations (e.g. {@link #LOCATION_ENE}) are used on a circular device. The + * locations for each are shown in the following image: + * + * Image detailing the locations of compass locations on a Wear device. North is at the top, followed by north-north-east, north-east, east-north-east, east, and so on. + * + *

The other locations (e.g. {@link #LOCATION_BOTTOM_CENTER}) are used on a rectilinear + * device. The locations for each are shown in the following image: + * + * Image detailing the locations of other buttons on a Wear device. The first word details the side of the device the button is on, then the second word details where on that side the button is (e.g. 'TOP LEFT' means on the top edge, at the left hand side, and 'RIGHT BOTTOM' means on the right edge, at the bottom). + * *

Common keycodes to use are {@link android.view.KeyEvent#KEYCODE_STEM_PRIMARY}, {@link * android.view.KeyEvent#KEYCODE_STEM_1}, {@link android.view.KeyEvent#KEYCODE_STEM_2}, and * {@link android.view.KeyEvent#KEYCODE_STEM_3}. @@ -722,6 +738,8 @@ * The location zone of the button as defined in the {@link #getButtonInfo(Context, int)} * method. The intended use is to help developers attach a friendly String to the button * location. This value is LOCATION_UNKNOWN if the information is not available. + * + * */ @ButtonLocation private final int mLocationZone;

diff --git a/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java b/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
index 7648800..c31650f 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
@@ -20,13 +20,12 @@
 import android.view.KeyEvent;
 import android.view.animation.Animation;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.annotation.UiThread;
 import androidx.wear.utils.ActivityAnimationUtil;
 
-import org.jetbrains.annotations.NotNull;
-
 /**
  * Controller that handles the back button click for dismiss the frame layout
  *
@@ -49,7 +48,7 @@
                         && dismiss());
     }
 
-    void disable(@NotNull DismissibleFrameLayout layout) {
+    void disable(@NonNull DismissibleFrameLayout layout) {
         setOnDismissListener(null);
         layout.setOnKeyListener(null);
         // setting this to false will also ensure that this view is not focusable in touch mode
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index f657cc2..23d44ca 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -167,4 +167,22 @@
             Assert.assertEquals("androidx.webkit.test", headerValue);
         }
     }
+
+    @Test
+    public void testEnterpriseAuthenticationAppLinkPolicyEnabled() throws Throwable {
+        WebkitUtils.checkFeature(WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY);
+
+        assertTrue(WebSettingsCompat.getEnterpriseAuthenticationAppLinkPolicyEnabled(
+                mWebViewOnUiThread.getSettings()));
+
+        WebSettingsCompat.setEnterpriseAuthenticationAppLinkPolicyEnabled(
+                mWebViewOnUiThread.getSettings(), false);
+        assertFalse(WebSettingsCompat.getEnterpriseAuthenticationAppLinkPolicyEnabled(
+                mWebViewOnUiThread.getSettings()));
+
+        WebSettingsCompat.setEnterpriseAuthenticationAppLinkPolicyEnabled(
+                mWebViewOnUiThread.getSettings(), true);
+        assertTrue(WebSettingsCompat.getEnterpriseAuthenticationAppLinkPolicyEnabled(
+                mWebViewOnUiThread.getSettings()));
+    }
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 477c312e..9e97fc8 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -724,6 +724,65 @@
         }
     }
 
+    /**
+     * Sets whether EnterpriseAuthenticationAppLinkPolicy if set by admin is allowed to have any
+     * effect on WebView.
+     * 

+ * EnterpriseAuthenticationAppLinkPolicy in WebView allows admins to specify authentication + * urls. When WebView is redirected to authentication url, and an app on the device has + * registered as the default handler for the url, that app is launched. + *

+ * EnterpriseAuthenticationAppLinkPolicy is enabled by default. + * + *

+ * This method should only be called if + * {@link WebViewFeature#isFeatureSupported(String)} + * returns true for {@link WebViewFeature#ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY}. + * + * @param enabled Whether EnterpriseAuthenticationAppLinkPolicy should be enabled. + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @RequiresFeature(name = WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, + enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported") + public static void setEnterpriseAuthenticationAppLinkPolicyEnabled( + @NonNull WebSettings settings, + boolean enabled) { + ApiFeature.NoFramework feature = + WebViewFeatureInternal.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY; + if (feature.isSupportedByWebView()) { + getAdapter(settings).setEnterpriseAuthenticationAppLinkPolicyEnabled(enabled); + } else { + throw WebViewFeatureInternal.getUnsupportedOperationException(); + } + } + + /** + * Gets whether EnterpriseAuthenticationAppLinkPolicy is allowed to have any effect on WebView. + * + *

+ * This method should only be called if + * {@link WebViewFeature#isFeatureSupported(String)} + * returns true for {@link WebViewFeature#ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY}. + * + * @return {@code true} if EnterpriseAuthenticationAppLinkPolicy is enabled and {@code false} + * otherwise. + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @RequiresFeature(name = WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, + enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported") + public static boolean getEnterpriseAuthenticationAppLinkPolicyEnabled( + @NonNull WebSettings settings) { + ApiFeature.NoFramework feature = + WebViewFeatureInternal.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY; + if (feature.isSupportedByWebView()) { + return getAdapter(settings).getEnterpriseAuthenticationAppLinkPolicyEnabled(); + } else { + throw WebViewFeatureInternal.getUnsupportedOperationException(); + } + } + private static WebSettingsAdapter getAdapter(WebSettings settings) { return WebViewGlueCommunicator.getCompatConverter().convertSettings(settings); }

diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 5de5148..8d50e6c 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -98,6 +98,7 @@
             GET_VARIATIONS_HEADER,
             ALGORITHMIC_DARKENING,
             REQUESTED_WITH_HEADER_CONTROL,
+            ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY,
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -492,6 +493,18 @@
     public static final String REQUESTED_WITH_HEADER_CONTROL = "REQUESTED_WITH_HEADER_CONTROL";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * This feature covers
+     * {@link WebSettingsCompat#setEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings, boolean)}and
+     * {@link WebSettingsCompat#getEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings)}.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
+            "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
+
+    /**
      * Return whether a feature is supported at run-time. On devices running Android version {@link
      * android.os.Build.VERSION_CODES#LOLLIPOP} and higher, this will check whether a feature is
      * supported, depending on the combination of the desired feature, the Android version of
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
index 963fb99..8d66c85 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
@@ -140,6 +140,22 @@
 
     /**
      * Adapter method for
+     * {@link androidx.webkit.WebSettingsCompat#setEnterpriseAuthenticationAppLinkPolicyEnabled}.
+     */
+    public void setEnterpriseAuthenticationAppLinkPolicyEnabled(boolean enabled) {
+        mBoundaryInterface.setEnterpriseAuthenticationAppLinkPolicyEnabled(enabled);
+    }
+
+    /**
+     * Adapter method for
+     * {@link androidx.webkit.WebSettingsCompat#getEnterpriseAuthenticationAppLinkPolicyEnabled}.
+     */
+    public boolean getEnterpriseAuthenticationAppLinkPolicyEnabled() {
+        return mBoundaryInterface.getEnterpriseAuthenticationAppLinkPolicyEnabled();
+    }
+
+    /**
+     * Adapter method for
      * {@link androidx.webkit.WebSettingsCompat#getRequestedWithHeaderMode(android.webkit.WebSettings)}
      */
     public int getRequestedWithHeaderMode() {
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index fc179e8..aa106c7 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -473,6 +473,15 @@
             new ApiFeature.NoFramework(WebViewFeature.REQUESTED_WITH_HEADER_CONTROL,
                     Features.REQUESTED_WITH_HEADER_CONTROL);
 
+
+    /**
+     * This feature covers
+     * {@link androidx.webkit.WebSettingsCompat#setEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings, boolean)} and
+     * {@link androidx.webkit.WebSettingsCompat#getEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings)}.
+     */
+    public static final ApiFeature.NoFramework ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
+            new ApiFeature.NoFramework(WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY,
+                    Features.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY);
     // --- Add new feature constants above this line ---
 
     private WebViewFeatureInternal() {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
index c90bcc8..ea3ad1f 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.work.WorkInfo.State
 import androidx.work.WorkManager.UpdateResult.APPLIED_FOR_NEXT_RUN
 import androidx.work.WorkManager.UpdateResult.APPLIED_IMMEDIATELY
@@ -35,6 +36,8 @@
 import androidx.work.impl.testutils.TestConstraintTracker
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
 import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import androidx.work.impl.workers.ARGUMENT_CLASS_NAME
+import androidx.work.impl.workers.ConstraintTrackingWorker
 import androidx.work.worker.LatchWorker
 import androidx.work.worker.RetryWorker
 import androidx.work.worker.TestWorker
@@ -413,8 +416,10 @@
         val spec = workManager.workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
         val delta = spec.calculateNextRunTime() - System.currentTimeMillis()
         assertThat(delta).isGreaterThan(0)
-        workManager.workDatabase.workSpecDao().setLastEnqueuedTime(request.stringId,
-            spec.lastEnqueueTime - delta)
+        workManager.workDatabase.workSpecDao().setLastEnqueuedTime(
+            request.stringId,
+            spec.lastEnqueueTime - delta
+        )
         val updated = OneTimeWorkRequest.Builder(TestWorker::class.java).setId(request.id)
             .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.DAYS)
             .build()
@@ -431,8 +436,10 @@
             .setInitialDelay(10, TimeUnit.MINUTES).build()
         val enqueueTime = System.currentTimeMillis()
         workManager.enqueue(request).result.get()
-        workManager.workDatabase.workSpecDao().setLastEnqueuedTime(request.stringId,
-            enqueueTime - TimeUnit.MINUTES.toMillis(5))
+        workManager.workDatabase.workSpecDao().setLastEnqueuedTime(
+            request.stringId,
+            enqueueTime - TimeUnit.MINUTES.toMillis(5)
+        )
         val updated = OneTimeWorkRequest.Builder(TestWorker::class.java)
             .setInitialDelay(20, TimeUnit.MINUTES)
             .setId(request.id)
@@ -446,6 +453,24 @@
         assertThat(delta).isLessThan(TimeUnit.MINUTES.toMillis(16))
     }
 
+    @Test
+    @MediumTest
+    @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
+    fun testUpdatePeriodicWorker_preservesConstraintTrackingWorker() {
+        val originRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setInitialDelay(10, TimeUnit.HOURS).build()
+        workManager.enqueue(originRequest).result.get()
+        val updateRequest = OneTimeWorkRequest.Builder(RetryWorker::class.java)
+            .setId(originRequest.id).setInitialDelay(10, TimeUnit.HOURS)
+            .setConstraints(Constraints(requiresBatteryNotLow = true))
+            .build()
+        workManager.updateWork(updateRequest).get()
+        val workSpec = db.workSpecDao().getWorkSpec(originRequest.stringId)!!
+        assertThat(workSpec.workerClassName).isEqualTo(ConstraintTrackingWorker::class.java.name)
+        assertThat(workSpec.input.getString(ARGUMENT_CLASS_NAME))
+            .isEqualTo(RetryWorker::class.java.name)
+    }
+
     @After
     fun tearDown() {
         workManager.cancelAllWork()
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
index 236e52c..b4435ea 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerUpdater.kt
@@ -23,6 +23,7 @@
 import androidx.work.WorkRequest
 import androidx.work.impl.model.WorkSpec
 import androidx.work.impl.utils.futures.SettableFuture
+import androidx.work.impl.utils.wrapInConstraintTrackingWorkerIfNeeded
 import com.google.common.util.concurrent.ListenableFuture
 
 private fun updateWorkImpl(
@@ -57,7 +58,8 @@
             runAttemptCount = oldWorkSpec.runAttemptCount,
             lastEnqueueTime = oldWorkSpec.lastEnqueueTime,
         )
-        workSpecDao.updateWorkSpec(updatedSpec)
+
+        workSpecDao.updateWorkSpec(wrapInConstraintTrackingWorkerIfNeeded(schedulers, updatedSpec))
         workTagDao.deleteByWorkSpecId(workSpecId)
         workTagDao.insertTags(workRequest)
         if (!isEnqueued) {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index cbd798f..5408bb6 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -25,24 +25,20 @@
 import static androidx.work.WorkInfo.State.FAILED;
 import static androidx.work.WorkInfo.State.RUNNING;
 import static androidx.work.WorkInfo.State.SUCCEEDED;
-import static androidx.work.impl.workers.ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME;
+import static androidx.work.impl.utils.EnqueueUtilsKt.wrapInConstraintTrackingWorkerIfNeeded;
 
 import android.content.Context;
-import android.os.Build;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
-import androidx.work.Constraints;
-import androidx.work.Data;
 import androidx.work.ExistingWorkPolicy;
 import androidx.work.Logger;
 import androidx.work.Operation;
 import androidx.work.WorkInfo;
 import androidx.work.WorkRequest;
 import androidx.work.impl.OperationImpl;
-import androidx.work.impl.Scheduler;
 import androidx.work.impl.Schedulers;
 import androidx.work.impl.WorkContinuationImpl;
 import androidx.work.impl.WorkDatabase;
@@ -53,7 +49,6 @@
 import androidx.work.impl.model.WorkName;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
-import androidx.work.impl.workers.ConstraintTrackingWorker;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -306,20 +301,17 @@
                 workSpec.lastEnqueueTime = currentTimeMillis;
             }
 
-            if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
-                    && Build.VERSION.SDK_INT <= 25) {
-                tryDelegateConstrainedWorkSpec(workSpec);
-            } else if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL
-                    && usesScheduler(workManagerImpl, Schedulers.GCM_SCHEDULER)) {
-                tryDelegateConstrainedWorkSpec(workSpec);
-            }
-
             // If we have one WorkSpec with an enqueued state, then we need to schedule.
             if (workSpec.state == ENQUEUED) {
                 needsScheduling = true;
             }
 
-            workDatabase.workSpecDao().insertWorkSpec(workSpec);
+            workDatabase.workSpecDao().insertWorkSpec(
+                    wrapInConstraintTrackingWorkerIfNeeded(
+                            workManagerImpl.getSchedulers(),
+                            workSpec
+                    )
+            );
 
             if (hasPrerequisite) {
                 for (String prerequisiteId : prerequisiteIds) {
@@ -335,50 +327,4 @@
         }
         return needsScheduling;
     }
-
-    private static void tryDelegateConstrainedWorkSpec(WorkSpec workSpec) {
-        // requiresBatteryNotLow and requiresStorageNotLow require API 26 for JobScheduler.
-        // Delegate to ConstraintTrackingWorker between API 23-25.
-        Constraints constraints = workSpec.constraints;
-        String workerClassName = workSpec.workerClassName;
-        // Check if the Worker is a ConstraintTrackingWorker already. Otherwise we could end up
-        // wrapping a ConstraintTrackingWorker with another and build a taller stack.
-        // This usually happens when a developer accidentally enqueues() a named WorkRequest
-        // with an ExistingWorkPolicy.KEEP and subsequent inserts no-op (while the state of the
-        // Worker is not ENQUEUED or RUNNING i.e. the Worker probably just got done & the app is
-        // holding on to a reference of WorkSpec which got updated). We end up reusing the
-        // WorkSpec, and get a ConstraintTrackingWorker (instead of the original Worker class).
-        boolean isConstraintTrackingWorker =
-                workerClassName.equals(ConstraintTrackingWorker.class.getName());
-        if (!isConstraintTrackingWorker
-                && (constraints.requiresBatteryNotLow() || constraints.requiresStorageNotLow())) {
-            Data.Builder builder = new Data.Builder();
-            // Copy all arguments
-            builder.putAll(workSpec.input)
-                    .putString(ARGUMENT_CLASS_NAME, workerClassName);
-            workSpec.workerClassName = ConstraintTrackingWorker.class.getName();
-            workSpec.input = builder.build();
-        }
-    }
-
-    /**
-     * @param className The fully qualified class name of the {@link Scheduler}
-     * @return {@code true} if the {@link Scheduler} class is being used by WorkManager.
-     */
-    private static boolean usesScheduler(
-            @NonNull WorkManagerImpl workManager,
-            @NonNull String className) {
-
-        try {
-            Class klass = Class.forName(className);
-            for (Scheduler scheduler : workManager.getSchedulers()) {
-                if (klass.isAssignableFrom(scheduler.getClass())) {
-                    return true;
-                }
-            }
-            return false;
-        } catch (ClassNotFoundException ignore) {
-            return false;
-        }
-    }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueUtils.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueUtils.kt
new file mode 100644
index 0000000..a6cc806
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueUtils.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.work.impl.utils
+
+import android.os.Build
+import androidx.work.Data
+import androidx.work.impl.Scheduler
+import androidx.work.impl.Schedulers
+import androidx.work.impl.WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL
+import androidx.work.impl.WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
+import androidx.work.impl.model.WorkSpec
+import androidx.work.impl.workers.ARGUMENT_CLASS_NAME
+import androidx.work.impl.workers.ConstraintTrackingWorker
+
+internal fun tryDelegateConstrainedWorkSpec(workSpec: WorkSpec): WorkSpec {
+    // requiresBatteryNotLow and requiresStorageNotLow require API 26 for JobScheduler.
+    // Delegate to ConstraintTrackingWorker between API 23-25.
+    val constraints = workSpec.constraints
+    val workerClassName = workSpec.workerClassName
+    // Check if the Worker is a ConstraintTrackingWorker already. Otherwise we could end up
+    // wrapping a ConstraintTrackingWorker with another and build a taller stack.
+    // This usually happens when a developer accidentally enqueues() a named WorkRequest
+    // with an ExistingWorkPolicy.KEEP and subsequent inserts no-op (while the state of the
+    // Worker is not ENQUEUED or RUNNING i.e. the Worker probably just got done & the app is
+    // holding on to a reference of WorkSpec which got updated). We end up reusing the
+    // WorkSpec, and get a ConstraintTrackingWorker (instead of the original Worker class).
+    val isConstraintTrackingWorker = workerClassName == ConstraintTrackingWorker::class.java.name
+    if (!isConstraintTrackingWorker &&
+        (constraints.requiresBatteryNotLow() || constraints.requiresStorageNotLow())
+    ) {
+        val newInputData = Data.Builder().putAll(workSpec.input)
+            .putString(ARGUMENT_CLASS_NAME, workerClassName)
+            .build()
+        return workSpec.copy(
+            input = newInputData,
+            workerClassName = ConstraintTrackingWorker::class.java.name
+        )
+    }
+    return workSpec
+}
+
+internal fun wrapInConstraintTrackingWorkerIfNeeded(
+    schedulers: List,
+    workSpec: WorkSpec,
+): WorkSpec {
+    return when {
+        Build.VERSION.SDK_INT in MIN_JOB_SCHEDULER_API_LEVEL..25 ->
+            tryDelegateConstrainedWorkSpec(workSpec)
+        Build.VERSION.SDK_INT <= MAX_PRE_JOB_SCHEDULER_API_LEVEL &&
+            usesScheduler(schedulers, Schedulers.GCM_SCHEDULER) ->
+            tryDelegateConstrainedWorkSpec(workSpec)
+        else -> workSpec
+    }
+}
+
+private fun usesScheduler(
+    schedulers: List,
+    className: String
+): Boolean {
+    return try {
+        val klass = Class.forName(className)
+        return schedulers.any { scheduler -> klass.isAssignableFrom(scheduler.javaClass) }
+    } catch (ignore: ClassNotFoundException) {
+        false
+    }
+}