Merge "Call `Snapshot.notifyObjectsInitialized` before drawing" into androidx-main
diff --git a/.github/actions/build-single-project/action.yml b/.github/actions/build-single-project/action.yml
index 969d470..8c7af75 100644
--- a/.github/actions/build-single-project/action.yml
+++ b/.github/actions/build-single-project/action.yml
@@ -33,7 +33,9 @@
       run: echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "cmake;3.22.1"
     - name: "Install NDK"
       shell: bash
-      run: echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "ndk;23.1.7779620"
+      run: |
+        NDK_VERSION=$(grep "ndkVersion" settings.gradle | awk -F "=" '{gsub(/"| /, ""); print $2}')
+        echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "ndk;$NDK_VERSION"
     - name: "Install Android SDK Build-Tools"
       shell: bash
       run: echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0-rc1"
diff --git a/biometric/biometric/src/main/res/values-iw/strings.xml b/biometric/biometric/src/main/res/values-iw/strings.xml
index c5e57f8..fd5271a 100644
--- a/biometric/biometric/src/main/res/values-iw/strings.xml
+++ b/biometric/biometric/src/main/res/values-iw/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"יש לגעת בחיישן טביעות האצבע"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"צריך לגעת בחיישן טביעות האצבע"</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"לא זוהתה"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"החומרה בשביל טביעת אצבע אינה זמינה."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"לא נרשמו טביעות אצבע."</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 2abce3c..cdbd64e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -530,14 +530,7 @@
         project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
             val kotlinCompilerArgs =
                 project.provider {
-                    val args =
-                        mutableListOf(
-                            "-Xskip-metadata-version-check",
-                        )
-                    // TODO (b/259578592): enable -Xjvm-default=all for camera-camera2-pipe projects
-                    if (!project.name.contains("camera-camera2-pipe")) {
-                        args += "-Xjvm-default=all"
-                    }
+                    val args = mutableListOf("-Xskip-metadata-version-check", "-Xjvm-default=all")
                     if (androidXExtension.type.targetsKotlinConsumersOnly) {
                         // The Kotlin Compiler adds intrinsic assertions which are only relevant
                         // when the code is consumed by Java users. Therefore we can turn this off
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/BinaryCompatibilityValidation.kt b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/BinaryCompatibilityValidation.kt
index f942c4b..94f0d30 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/BinaryCompatibilityValidation.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/BinaryCompatibilityValidation.kt
@@ -27,11 +27,11 @@
 import androidx.build.checkapi.getBuiltBcvFileDirectory
 import androidx.build.checkapi.getRequiredCompatibilityApiFileFromDir
 import androidx.build.checkapi.shouldWriteVersionedApiFile
+import androidx.build.metalava.UpdateApiTask
 import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import androidx.build.version
 import com.android.utils.appendCapitalized
 import kotlinx.validation.KlibDumpMetadata
-import kotlinx.validation.KotlinApiCompareTask
 import kotlinx.validation.KotlinKlibAbiBuildTask
 import kotlinx.validation.KotlinKlibExtractAbiTask
 import kotlinx.validation.KotlinKlibMergeAbiTask
@@ -87,6 +87,11 @@
             project.tasks.named("check").configure { it.dependsOn(checkAll) }
             project.addToCheckTask(checkAll)
             project.addToBuildOnServer(checkAll)
+            if (HostManager.hostIsMac) {
+                project.tasks.named("updateApi", UpdateApiTask::class.java) {
+                    it.dependsOn(updateAll)
+                }
+            }
         }
 
     private fun configureKlibTasks(
@@ -147,10 +152,10 @@
         project.tasks
             .register(
                 CHECK_NAME.appendCapitalized(NATIVE_SUFFIX),
-                KotlinApiCompareTask::class.java
+                CheckAbiEquivalenceTask::class.java
             ) {
-                it.projectApiFile.set(projectApiFile.map { fileProperty -> fileProperty.get() })
-                it.generatedApiFile.set(generatedApiFile.map { fileProperty -> fileProperty.get() })
+                it.checkedInDump = projectApiFile
+                it.builtDump = generatedApiFile
                 it.group = ABI_GROUP_NAME
             }
             .also { task -> task.configure { it.cacheEvenIfNoOutputs() } }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/CheckApiEquivalenceTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/CheckApiEquivalenceTask.kt
new file mode 100644
index 0000000..9ff9c13
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/binarycompatibilityvalidator/CheckApiEquivalenceTask.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.binarycompatibilityvalidator
+
+import androidx.build.metalava.checkEqual
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+
+@CacheableTask
+abstract class CheckAbiEquivalenceTask : DefaultTask() {
+
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    @get:InputFile
+    abstract var checkedInDump: Provider<RegularFileProperty>
+
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    @get:InputFile
+    abstract var builtDump: Provider<RegularFileProperty>
+
+    @TaskAction
+    fun execute() {
+        checkEqual(checkedInDump.get().asFile.get(), builtDump.get().asFile.get(), "updateAbi")
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index db10029..46af070 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -105,7 +105,8 @@
             when (plugin) {
                 is LibraryPlugin -> {
                     val libraryExtension = project.extensions.getByType<LibraryExtension>()
-                    libraryExtension.compileSdk = project.defaultAndroidConfig.compileSdk
+                    libraryExtension.compileSdk =
+                        project.defaultAndroidConfig.latestStableCompileSdk
                     libraryExtension.buildToolsVersion =
                         project.defaultAndroidConfig.buildToolsVersion
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/CheckApiEquivalenceTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/CheckApiEquivalenceTask.kt
index 0e586fb..f227d55 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/CheckApiEquivalenceTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/CheckApiEquivalenceTask.kt
@@ -90,7 +90,7 @@
     return diffLines.joinToString("\n")
 }
 
-fun checkEqual(expected: File, actual: File) {
+fun checkEqual(expected: File, actual: File, updateTaskName: String = "updateApi") {
     if (!FileUtils.contentEquals(expected, actual)) {
         val diff = summarizeDiff(expected, actual)
         val message =
@@ -99,7 +99,7 @@
                     Declared definition is $expected
                     True     definition is $actual
 
-                    Please run `./gradlew updateApi` to confirm these changes are
+                    Please run `./gradlew ${updateTaskName}` to confirm these changes are
                     intentional by updating the API definition.
 
                     Difference between these files:
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
index 5667601..85f598f 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
@@ -33,6 +33,12 @@
         sdkString.toInt()
     }
 
+    override val latestStableCompileSdk: Int by lazy {
+        val sdkString = project.extraPropertyOrNull(LATEST_STABLE_COMPILE_SDK)?.toString()
+        check(sdkString != null) { "$LATEST_STABLE_COMPILE_SDK is unset" }
+        sdkString.toInt()
+    }
+
     override val minSdk: Int = 21
 
     override val targetSdk: Int by lazy {
@@ -41,6 +47,7 @@
 
     companion object {
         private const val COMPILE_SDK = "androidx.compileSdk"
+        private const val LATEST_STABLE_COMPILE_SDK = "androidx.latestStableCompileSdk"
         private const val TARGET_SDK_VERSION = "androidx.targetSdkVersion"
 
         /**
@@ -50,6 +57,7 @@
         val GRADLE_PROPERTIES =
             listOf(
                 COMPILE_SDK,
+                LATEST_STABLE_COMPILE_SDK,
                 TARGET_SDK_VERSION,
             )
     }
@@ -70,6 +78,13 @@
      */
     val compileSdk: Int
 
+    /**
+     * The latest stable compile SDK version that is available to use for AndroidX projects.
+     *
+     * This may be specified in `gradle.properties` using `androidx.latestStableCompileSdk`.
+     */
+    val latestStableCompileSdk: Int
+
     /** Default minimum SDK version used for AndroidX projects. */
     val minSdk: Int
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ApiCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ApiCompat.kt
index 5657c07..f5f631e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ApiCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ApiCompat.kt
@@ -17,10 +17,13 @@
 package androidx.camera.camera2.pipe.integration.compat
 
 import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
 import android.view.Surface
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 
 @RequiresApi(Build.VERSION_CODES.N)
 internal object Api24Compat {
@@ -48,4 +51,17 @@
     ) {
         callback.onReadoutStarted(session, request, timestamp, frameNumber)
     }
+
+    @JvmStatic
+    fun isZoomOverrideAvailable(cameraProperties: CameraProperties): Boolean =
+        cameraProperties.metadata[CameraCharacteristics.CONTROL_AVAILABLE_SETTINGS_OVERRIDES]
+            ?.contains(CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM) ?: false
+
+    @JvmStatic
+    fun setSettingsOverrideZoom(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            parameters[CaptureRequest.CONTROL_SETTINGS_OVERRIDE] =
+                CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM
+        }
+    }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/Camera2CameraControlCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/Camera2CameraControlCompat.kt
index cc4813e..bf98070 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/Camera2CameraControlCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/Camera2CameraControlCompat.kt
@@ -40,6 +40,7 @@
 
 private const val TAG_KEY = "Camera2CameraControl.tag"
 
+@JvmDefaultWithCompatibility
 @ExperimentalCamera2Interop
 public interface Camera2CameraControlCompat : Request.Listener {
     public fun addRequestOption(bundle: CaptureRequestOptions)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ZoomCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ZoomCompat.kt
index 43262e0..c04fcf7 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ZoomCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/ZoomCompat.kt
@@ -119,6 +119,10 @@
     private val cameraProperties: CameraProperties,
     private val range: Range<Float>,
 ) : ZoomCompat {
+    private val shouldOverrideZoom =
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
+            Api34Compat.isZoomOverrideAvailable(cameraProperties)
+
     override val minZoomRatio: Float
         get() = range.lower
 
@@ -127,7 +131,12 @@
 
     override fun applyAsync(zoomRatio: Float, camera: UseCaseCamera): Deferred<Unit> {
         require(zoomRatio in minZoomRatio..maxZoomRatio)
-        return camera.setParameterAsync(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio)
+        val parameters: MutableMap<CaptureRequest.Key<*>, Any> =
+            mutableMapOf(CaptureRequest.CONTROL_ZOOM_RATIO to zoomRatio)
+        if (shouldOverrideZoom && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            Api34Compat.setSettingsOverrideZoom(parameters)
+        }
+        return camera.setParametersAsync(parameters)
     }
 
     override fun getCropSensorRegion(): Rect =
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt
index 4ebe347..a8cf85c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt
@@ -26,6 +26,7 @@
  * [CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD].
  * - Subclasses of this quirk may contain device specific information.
  */
+@JvmDefaultWithCompatibility
 public interface CaptureIntentPreviewQuirk : Quirk {
     /**
      * Returns if the device specific issue can be workaround by using
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index b19bfe9..6fbcac4 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -47,6 +47,7 @@
 internal val defaultOptionPriority = Config.OptionPriority.OPTIONAL
 internal const val defaultTemplate = CameraDevice.TEMPLATE_PREVIEW
 
+@JvmDefaultWithCompatibility
 public interface UseCaseCamera {
     // UseCases
     public var runningUseCases: Set<UseCase>
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index 9024f2d..6aa775b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -57,6 +57,7 @@
  * The parameters can be stored for the different types of config respectively. Each type of the
  * config can be removed or overridden respectively without interfering with the other types.
  */
+@JvmDefaultWithCompatibility
 public interface UseCaseCameraRequestControl {
     /** The declaration order is the ordering to merge. */
     public enum class Type {
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
index cdee6ee..f2d268a 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
@@ -44,8 +44,7 @@
     cameraContext: CameraContext,
     private val graphId: CameraGraphId,
     private val graphConfig: CameraGraph.Config,
-    private val graphListener: GraphListener,
-    private val streamGraph: StreamGraph,
+    private val graphListener: GraphListener
 ) : CameraController {
     override val cameraId: CameraId
         get() = graphConfig.camera
@@ -76,6 +75,14 @@
     public var currentCaptureSequenceProcessor: FakeCaptureSequenceProcessor? = null
         private set
 
+    public var outputLatencySet: StreamGraph.OutputLatency? = null
+        private set
+
+    public var streamGraph: StreamGraph? = null
+
+    public val simulatedCaptureLatency: Long = 5L
+    public val simulatedProcessingLatency: Long = 10L
+
     init {
         check(cameraContext.cameraBackends.allIds.isNotEmpty()) {
             "Backends provided by cameraContext.cameraBackends cannot be empty"
@@ -146,6 +153,11 @@
         }
     }
 
+    public fun simulateOutputLatency() {
+        outputLatencySet =
+            StreamGraph.OutputLatency(simulatedCaptureLatency, simulatedProcessingLatency)
+    }
+
     override fun start() {
         synchronized(lock) {
             check(!closed) { "Attempted to invoke start after close." }
@@ -176,7 +188,7 @@
     }
 
     override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
-        check(streamGraph.streamIds.containsAll(surfaceMap.keys))
+        streamGraph?.streamIds?.containsAll(surfaceMap.keys).let { check(it == true) }
 
         synchronized(lock) {
             currentSurfaceMap = surfaceMap
@@ -189,4 +201,8 @@
             }
         }
     }
+
+    override fun getOutputLatency(streamId: StreamId?): StreamGraph.OutputLatency? {
+        return outputLatencySet
+    }
 }
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
index 12ecb03..56f4beb 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
@@ -72,13 +72,8 @@
         streamGraph: StreamGraph
     ): CameraController {
         val cameraController =
-            CameraControllerSimulator(
-                cameraContext,
-                graphId,
-                graphConfig,
-                graphListener,
-                streamGraph
-            )
+            CameraControllerSimulator(cameraContext, graphId, graphConfig, graphListener)
+        cameraController.streamGraph = streamGraph
         synchronized(lock) { _cameraControllers.add(cameraController) }
         return cameraController
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 513473b..9ed683c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -57,6 +57,7 @@
  * The lifecycle of an individual camera is managed by [CameraController]s, which may be created via
  * [CameraBackend.createCameraController].
  */
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface CameraBackend {
     public val id: CameraBackendId
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
index fa3e7ca..ae5b2ae 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
@@ -85,6 +85,12 @@
     public fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>)
 
     /**
+     * Get the estimated real time latency for an extension session. This method returns null if the
+     * [StreamGraph] is not configured correctly or the CaptureSession is not ready.
+     */
+    public fun getOutputLatency(streamId: StreamId?): StreamGraph.OutputLatency?
+
+    /**
      * ControllerState indicates the internal state of a [CameraController]. These states are needed
      * to make sure we only invoke [CameraController] methods under the right conditions.
      *
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
index 90adc9c..db30b27 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
@@ -23,6 +23,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flow
 
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 /** Methods for querying, iterating, and selecting the Cameras that are available on the device. */
 public interface CameraDevices {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index f876917..e27775d 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -419,6 +419,7 @@
      * While this object is thread-safe, it should not shared or held for long periods of time.
      * Example: A [Session] should *not* be held during video recording.
      */
+    @JvmDefaultWithCompatibility
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public interface Session : AutoCloseable {
         /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt
index 7485d71..163d890 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frame.kt
@@ -67,6 +67,7 @@
  * }
  * ```
  */
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface Frame : FrameReference, AutoCloseable {
     /**
@@ -264,6 +265,7 @@
  * A FrameReference is a weak reference to a [Frame]. It will not prevent the underlying frame from
  * being closed or released unless the frame is acquired via [acquire] or [tryAcquire].
  */
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface FrameReference {
     /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt
index 6079272..e9d51e2 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt
@@ -99,6 +99,7 @@
         check(future >= 0)
     }
 
+    @JvmDefaultWithCompatibility
     public interface TransformFn {
         public fun computeOverridesFor(
             result: FrameInfo,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
index 36d683a..d75b9f9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
@@ -71,6 +71,7 @@
      * in a repeating request may be issued multiple times within the same session, and should not
      * rely on [onRequestSequenceSubmitted] from being invoked only once.
      */
+    @JvmDefaultWithCompatibility
     public interface Listener {
         /**
          * This event indicates that the camera sensor has started exposing the frame associated
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamGraph.kt
index 37fefc4..8f7bdcf 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamGraph.kt
@@ -16,6 +16,7 @@
 
 package androidx.camera.camera2.pipe
 
+import android.hardware.camera2.CameraExtensionSession
 import androidx.annotation.RestrictTo
 
 /**
@@ -23,6 +24,7 @@
  *
  * [CameraStream]s can be used to build [Request]s that are sent to a [CameraGraph].
  */
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface StreamGraph {
     public val streams: List<CameraStream>
@@ -35,4 +37,21 @@
     public operator fun get(streamId: StreamId): CameraStream? = streams.find { it.id == streamId }
 
     public operator fun get(outputId: OutputId): OutputStream? = outputs.find { it.id == outputId }
+
+    /**
+     * Get the estimated real time latency for an extension session or output stall duration for a
+     * regular session. This method accepts an [OutputId] for MultiResolution use cases when there
+     * are multiple streams. This method returns null if the [StreamGraph] is not configured
+     * correctly or if the Android version is under 34 for extensions.
+     */
+    public fun getOutputLatency(streamId: StreamId, outputId: OutputId? = null): OutputLatency?
+
+    /** Wrapper class for [CameraExtensionSession.StillCaptureLatency] object. */
+    public data class OutputLatency(
+        public val estimatedCaptureLatencyNs: Long,
+        public val estimatedProcessingLatencyNs: Long
+    ) {
+        public val estimatedLatencyNs: Long
+            get() = estimatedCaptureLatencyNs + estimatedProcessingLatencyNs
+    }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
index 805aff9..bb2cb3f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
@@ -151,6 +151,7 @@
  * the underlying HAL on the device may produce different sized images for the same request. This
  * represents one of those potential outputs.
  */
+@JvmDefaultWithCompatibility
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface OutputStream {
     // Every output comes from one, and exactly one, CameraStream
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
index 3ab4eb5..8bcf0b3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
@@ -34,7 +34,6 @@
 import android.hardware.camera2.params.SessionConfiguration
 import android.media.ImageReader
 import android.media.ImageWriter
-import android.os.Build
 import android.os.Handler
 import android.util.Size
 import android.view.Surface
@@ -43,7 +42,7 @@
 import androidx.camera.camera2.pipe.CameraMetadata
 import java.util.concurrent.Executor
 
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(23)
 internal object Api23Compat {
     @JvmStatic
     @Throws(CameraAccessException::class)
@@ -100,7 +99,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.N)
+@RequiresApi(24)
 internal object Api24Compat {
     @JvmStatic
     @Throws(CameraAccessException::class)
@@ -142,7 +141,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.O)
+@RequiresApi(26)
 internal object Api26Compat {
     @JvmStatic
     @Throws(CameraAccessException::class)
@@ -172,9 +171,17 @@
     fun addSurfaces(outputConfig: OutputConfiguration, surface: Surface) {
         return outputConfig.addSurface(surface)
     }
+
+    @JvmStatic
+    fun onCaptureQueueEmpty(
+        interopSessionStateCallback: CameraCaptureSession.StateCallback?,
+        session: CameraCaptureSession,
+    ) {
+        interopSessionStateCallback?.onCaptureQueueEmpty(session)
+    }
 }
 
-@RequiresApi(Build.VERSION_CODES.P)
+@RequiresApi(28)
 @Suppress("DEPRECATION")
 internal object Api28Compat {
     @JvmStatic
@@ -277,7 +284,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.Q)
+@RequiresApi(29)
 internal object Api29Compat {
     @JvmStatic
     fun imageReaderNewInstance(
@@ -296,7 +303,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.R)
+@RequiresApi(30)
 internal object Api30Compat {
     @JvmStatic
     fun getConcurrentCameraIds(cameraManager: CameraManager): Set<Set<String>> {
@@ -314,7 +321,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.S)
+@RequiresApi(31)
 internal object Api31Compat {
     @JvmStatic
     fun newInputConfiguration(
@@ -404,7 +411,7 @@
     ): List<Size> = extensionCharacteristics.getExtensionSupportedSizes(extension, klass)
 }
 
-@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+@RequiresApi(33)
 internal object Api33Compat {
     @JvmStatic
     fun setDynamicRangeProfile(outputConfig: OutputConfiguration, dynamicRangeProfile: Long) {
@@ -488,7 +495,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@RequiresApi(34)
 internal object Api34Compat {
     @JvmStatic
     fun isPostviewAvailable(
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
index f8473a0..16a26fd 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
@@ -27,6 +27,7 @@
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraStatusMonitor.CameraStatus
 import androidx.camera.camera2.pipe.CameraSurfaceManager
+import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.config.Camera2ControllerScope
 import androidx.camera.camera2.pipe.core.Log
@@ -227,6 +228,15 @@
             ?.configureSurfaceMap(surfaceMap)
     }
 
+    override fun getOutputLatency(streamId: StreamId?): StreamGraph.OutputLatency? {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            return currentSession?.getRealtimeCaptureLatency().let {
+                StreamGraph.OutputLatency(it?.captureLatency ?: 0, it?.processingLatency ?: 0)
+            }
+        }
+        return null
+    }
+
     private suspend fun bindSessionToCamera() {
         val camera: VirtualCamera?
         val session: CaptureSessionState?
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
index bb778e1..41bd2816 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
@@ -36,7 +36,7 @@
  * that are either expensive to create and access, or that only exist on newer versions of the OS.
  * This allows all fields to be accessed and return reasonable values on all OS versions.
  */
-@RequiresApi(Build.VERSION_CODES.S)
+@RequiresApi(31)
 internal class Camera2CameraExtensionMetadata(
     override val camera: CameraId,
     override val isRedacted: Boolean,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
index 0830885..54f4524 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
@@ -31,6 +31,7 @@
 import javax.inject.Singleton
 import kotlinx.atomicfu.atomic
 
+@JvmDefaultWithCompatibility
 internal interface Camera2DeviceCloser {
     fun closeCamera(
         cameraDeviceWrapper: CameraDeviceWrapper? = null,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index 3ee3ee4..19ff6a7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -211,7 +211,7 @@
         }
     }
 
-    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresApi(31)
     private fun createCameraExtensionMetadata(
         cameraId: CameraId,
         redacted: Boolean,
@@ -255,7 +255,7 @@
         }
     }
 
-    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresApi(31)
     private fun getCameraExtensionCharacteristics(
         cameraId: CameraId
     ): CameraExtensionCharacteristics {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
index 0455af7..da4c9b8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
@@ -54,7 +54,7 @@
     fun createCaptureRequest(template: RequestTemplate): CaptureRequest.Builder?
 
     /** @see CameraDevice.createReprocessCaptureRequest */
-    @RequiresApi(Build.VERSION_CODES.M)
+    @RequiresApi(23)
     fun createReprocessCaptureRequest(inputResult: TotalCaptureResult): CaptureRequest.Builder?
 
     /** @see CameraDevice.createCaptureSession */
@@ -64,7 +64,7 @@
     ): Boolean
 
     /** @see CameraDevice.createReprocessableCaptureSession */
-    @RequiresApi(Build.VERSION_CODES.M)
+    @RequiresApi(23)
     fun createReprocessableCaptureSession(
         input: InputConfiguration,
         outputs: List<Surface>,
@@ -72,21 +72,21 @@
     ): Boolean
 
     /** @see CameraDevice.createConstrainedHighSpeedCaptureSession */
-    @RequiresApi(Build.VERSION_CODES.M)
+    @RequiresApi(23)
     fun createConstrainedHighSpeedCaptureSession(
         outputs: List<Surface>,
         stateCallback: CameraCaptureSessionWrapper.StateCallback
     ): Boolean
 
     /** @see CameraDevice.createCaptureSessionByOutputConfigurations */
-    @RequiresApi(Build.VERSION_CODES.N)
+    @RequiresApi(24)
     fun createCaptureSessionByOutputConfigurations(
         outputConfigurations: List<OutputConfigurationWrapper>,
         stateCallback: CameraCaptureSessionWrapper.StateCallback
     ): Boolean
 
     /** @see CameraDevice.createReprocessableCaptureSessionByConfigurations */
-    @RequiresApi(Build.VERSION_CODES.N)
+    @RequiresApi(24)
     fun createReprocessableCaptureSessionByConfigurations(
         inputConfig: InputConfigData,
         outputs: List<OutputConfigurationWrapper>,
@@ -94,17 +94,16 @@
     ): Boolean
 
     /** @see CameraDevice.createCaptureSession */
-    @RequiresApi(Build.VERSION_CODES.P) fun createCaptureSession(config: SessionConfigData): Boolean
+    @RequiresApi(28) fun createCaptureSession(config: SessionConfigData): Boolean
 
     /** @see CameraDevice.createExtensionSession */
-    @RequiresApi(Build.VERSION_CODES.S)
-    fun createExtensionSession(config: ExtensionSessionConfigData): Boolean
+    @RequiresApi(31) fun createExtensionSession(config: ExtensionSessionConfigData): Boolean
 
     /** Invoked when the [CameraDevice] has been closed */
     fun onDeviceClosed()
 
     /** @see CameraDevice.getCameraAudioRestriction */
-    @RequiresApi(Build.VERSION_CODES.R) fun getCameraAudioRestriction(): AudioRestrictionMode
+    @RequiresApi(30) fun getCameraAudioRestriction(): AudioRestrictionMode
 }
 
 internal fun CameraDevice?.closeWithTrace() {
@@ -161,7 +160,7 @@
         return result != null
     }
 
-    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresApi(31)
     override fun createExtensionSession(config: ExtensionSessionConfigData): Boolean {
         checkNotNull(config.extensionStateCallback) {
             "extensionStateCallback must be set to create Extension session"
@@ -461,13 +460,13 @@
             Api23Compat.createReprocessCaptureRequest(cameraDevice, inputResult)
         }
 
-    @RequiresApi(Build.VERSION_CODES.R)
+    @RequiresApi(30)
     override fun getCameraAudioRestriction(): AudioRestrictionMode =
         Debug.trace("getCameraAudioRestriction") {
             AudioRestrictionMode(Api30Compat.getCameraAudioRestriction(cameraDevice))
         }
 
-    @RequiresApi(Build.VERSION_CODES.R)
+    @RequiresApi(30)
     override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
         Debug.trace("setCameraAudioRestriction") {
             catchAndReportCameraExceptions(cameraId, cameraErrorListener) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
index 4a67eab..ba37301 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
@@ -110,7 +110,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(23)
 internal class AndroidMSessionFactory
 @Inject
 constructor(private val threads: Threads, private val graphConfig: CameraGraph.Config) :
@@ -153,7 +153,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(23)
 internal class AndroidMHighSpeedSessionFactory @Inject constructor(private val threads: Threads) :
     CaptureSessionFactory {
     override fun create(
@@ -177,7 +177,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.N)
+@RequiresApi(24)
 internal class AndroidNSessionFactory
 @Inject
 constructor(
@@ -233,7 +233,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.P)
+@RequiresApi(28)
 internal class AndroidPSessionFactory
 @Inject
 constructor(
@@ -310,7 +310,7 @@
     }
 }
 
-@RequiresApi(Build.VERSION_CODES.N)
+@RequiresApi(24)
 internal fun buildOutputConfigurations(
     graphConfig: CameraGraph.Config,
     streamGraph: StreamGraphImpl,
@@ -427,7 +427,7 @@
     return OutputConfigurations(allOutputs, deferredOutputs, postviewOutput)
 }
 
-@RequiresApi(Build.VERSION_CODES.S)
+@RequiresApi(31)
 internal class AndroidExtensionSessionFactory
 @Inject
 constructor(
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
index 11dd2b7..c6bf514 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
@@ -17,6 +17,8 @@
 package androidx.camera.camera2.pipe.compat
 
 import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraExtensionSession
+import android.os.Build
 import android.view.Surface
 import androidx.annotation.GuardedBy
 import androidx.camera.camera2.pipe.CameraGraph
@@ -138,6 +140,14 @@
         }
     }
 
+    fun getRealtimeCaptureLatency(): CameraExtensionSession.StillCaptureLatency? {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            val extensionSession = cameraCaptureSession?.session as? AndroidCameraExtensionSession
+            return extensionSession?.getRealTimeCaptureLatency()
+        }
+        return null
+    }
+
     override fun onActive(session: CameraCaptureSessionWrapper) {
         Log.debug { "$this Active" }
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
index 8f405d7..95c6641 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
@@ -188,7 +188,7 @@
     override fun onCaptureQueueEmpty(session: CameraCaptureSession) {
         stateCallback.onCaptureQueueEmpty(getWrapped(session, cameraErrorListener))
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            Api26CompatImpl.onCaptureQueueEmpty(session, interopSessionStateCallback)
+            Api26Compat.onCaptureQueueEmpty(interopSessionStateCallback, session)
         }
     }
 
@@ -240,17 +240,6 @@
         val previousSession = _lastStateCallback.getAndSet(null)
         previousSession?.let { previousSession.onSessionFinalized() }
     }
-
-    @RequiresApi(Build.VERSION_CODES.O)
-    private object Api26CompatImpl {
-        @JvmStatic
-        fun onCaptureQueueEmpty(
-            session: CameraCaptureSession,
-            interopSessionStateCallback: CameraCaptureSession.StateCallback?
-        ) {
-            interopSessionStateCallback?.onCaptureQueueEmpty(session)
-        }
-    }
 }
 
 internal open class AndroidCameraCaptureSession(
@@ -393,6 +382,17 @@
                     " was destroyed before calling createHighSpeedRequestList."
             }
             null
+        } catch (e: UnsupportedOperationException) {
+
+            // b/358592149: When a high speed session is closed, and then another high speed session
+            // is opened, the resources from the previous session might not be available yet.
+            // Since Camera2CaptureSequenceProcessor will try to create the session again, log
+            // and rethrow the error as a standard exception that can be ignored.
+            Log.warn {
+                "Failed to createHighSpeedRequestList from $device because the output surface" +
+                    " was not available."
+            }
+            null
         }
 
     @Suppress("UNCHECKED_CAST")
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
index 0c86a32..7fb4d94 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
@@ -63,6 +63,8 @@
         /** @see CameraExtensionSession.StateCallback.onConfigured */
         fun onConfigured(session: CameraExtensionSessionWrapper)
     }
+
+    fun getRealTimeCaptureLatency(): CameraExtensionSession.StillCaptureLatency?
 }
 
 @RequiresApi(31)
@@ -248,6 +250,13 @@
         return cameraExtensionSession.close()
     }
 
+    override fun getRealTimeCaptureLatency(): CameraExtensionSession.StillCaptureLatency? {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            return cameraExtensionSession.realtimeStillCaptureLatency
+        }
+        return null
+    }
+
     inner class Camera2CaptureSessionCallbackToExtensionCaptureCallback(
         private val captureCallback: Camera2CaptureCallback,
         private val frameQueue: Queue<Long>
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
index 23f8c0b..0379996 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
@@ -33,6 +33,7 @@
 import androidx.camera.camera2.pipe.RequestNumber
 import androidx.camera.camera2.pipe.RequestProcessor
 import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.graph.GraphListener
@@ -44,7 +45,7 @@
     private val graphId: CameraGraphId,
     private val graphConfig: CameraGraph.Config,
     private val graphListener: GraphListener,
-    private val requestProcessor: RequestProcessor
+    requestProcessor: RequestProcessor
 ) : CameraController {
     private val sequenceProcessor = ExternalCaptureSequenceProcessor(graphConfig, requestProcessor)
     private val graphProcessor: GraphRequestProcessor =
@@ -83,6 +84,10 @@
     override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
         sequenceProcessor.surfaceMap = surfaceMap
     }
+
+    override fun getOutputLatency(streamId: StreamId?): StreamGraph.OutputLatency? {
+        return null
+    }
 }
 
 @Suppress("DEPRECATION")
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
index a78a026..66b1de7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
@@ -108,6 +108,7 @@
  * Disconnecting the VirtualCamera will cause an artificial close events to be generated on the
  * state property, but may not cause the underlying [CameraDevice] to be closed.
  */
+@JvmDefaultWithCompatibility
 internal interface VirtualCamera {
     val state: Flow<CameraState>
     val value: CameraState
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
index 3121d7e..03ad670 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphListener.kt
@@ -19,6 +19,7 @@
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.GraphState.GraphStateError
 
+@JvmDefaultWithCompatibility
 public interface GraphListener {
     /**
      * Used to indicate that the graph is starting. This is called immediately when a [CameraGraph]
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
index 72a8e53..86dbf23 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
@@ -16,10 +16,12 @@
 
 package androidx.camera.camera2.pipe.graph
 
+import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.params.OutputConfiguration
 import android.os.Build
 import android.util.Size
 import android.view.Surface
+import androidx.camera.camera2.pipe.CameraController
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
@@ -37,6 +39,7 @@
 import androidx.camera.camera2.pipe.compat.Api24Compat
 import androidx.camera.camera2.pipe.config.CameraGraphScope
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.atomicfu.atomic
 
 /**
@@ -48,8 +51,9 @@
 internal class StreamGraphImpl
 @Inject
 constructor(
-    cameraMetadata: CameraMetadata,
-    graphConfig: CameraGraph.Config,
+    val cameraMetadata: CameraMetadata,
+    val graphConfig: CameraGraph.Config,
+    private val cameraControllerProvider: Provider<CameraController>,
 ) : StreamGraph {
     private val _streamMap: Map<CameraStream.Config, CameraStream>
 
@@ -63,6 +67,33 @@
 
     override fun get(config: CameraStream.Config): CameraStream? = _streamMap[config]
 
+    override fun getOutputLatency(
+        streamId: StreamId,
+        outputId: OutputId?
+    ): StreamGraph.OutputLatency? {
+        val cameraController = cameraControllerProvider.get()
+        val outputLatency = cameraController.getOutputLatency(streamId)
+        if (outputLatency != null) {
+            return outputLatency
+        }
+        val stream = this[streamId]
+        var output = outputId?.let { get(it) }
+        checkNotNull(stream) { "No stream found for given streamId $streamId" }
+        if (stream.outputs.size == 1) {
+            output = stream.outputs.single()
+        } else {
+            checkNotNull(output) {
+                "Output must be specified for MultiResolution use case. " +
+                    "No output found for given outputId $outputId"
+            }
+        }
+        val streamConfigurationMap =
+            cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+        val stallDuration =
+            streamConfigurationMap?.getOutputStallDuration(output.format.value, output.size)
+        return stallDuration?.let { StreamGraph.OutputLatency(it, 0) }
+    }
+
     init {
         val outputConfigListBuilder = mutableListOf<OutputConfig>()
         val outputConfigMap = mutableMapOf<OutputStream.Config, OutputConfig>()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraErrorListener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraErrorListener.kt
index f34f005..1b17870 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraErrorListener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraErrorListener.kt
@@ -23,6 +23,7 @@
  * Interface intended to be used to report camera errors. It will ensure only the current
  * [androidx.camera.camera2.pipe.graph.GraphListener] is notified of the error.
  */
+@JvmDefaultWithCompatibility
 public interface CameraErrorListener {
     public fun onCameraError(
         cameraId: CameraId,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageReaders.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageReaders.kt
index 48f05a2..f28a096 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageReaders.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageReaders.kt
@@ -197,7 +197,7 @@
 }
 
 /** Implements an [ImageReaderWrapper] using a [MultiResolutionImageReader]. */
-@RequiresApi(Build.VERSION_CODES.S)
+@RequiresApi(31)
 public class AndroidMultiResolutionImageReader(
     private val multiResolutionImageReader: MultiResolutionImageReader,
     private val streamFormat: StreamFormat,
@@ -268,7 +268,7 @@
     }
 
     public companion object {
-        @RequiresApi(Build.VERSION_CODES.S)
+        @RequiresApi(31)
         public fun create(
             outputFormat: Int,
             streamId: StreamId,
@@ -305,7 +305,7 @@
             return androidMultiResolutionImageReader
         }
 
-        @RequiresApi(Build.VERSION_CODES.S)
+        @RequiresApi(31)
         public fun create(
             cameraStream: CameraStream,
             capacity: Int,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
index 171ad15..3d04635 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
@@ -31,7 +31,7 @@
 import kotlinx.atomicfu.atomic
 
 /** Implements an [ImageWriterWrapper] using an [ImageWriter]. */
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(23)
 public class AndroidImageWriter
 private constructor(
     private val imageWriter: ImageWriter,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/ImageWriterWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/ImageWriterWrapper.kt
index cdb9a0b..faae846 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/ImageWriterWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/ImageWriterWrapper.kt
@@ -17,13 +17,12 @@
 package androidx.camera.camera2.pipe.media
 
 import android.media.ImageWriter
-import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.InputStreamId
 import androidx.camera.camera2.pipe.UnsafeWrapper
 
 /** Simplified wrapper for [ImageWriter]-like classes. */
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(23)
 public interface ImageWriterWrapper : UnsafeWrapper, AutoCloseable {
 
     /**
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
index b3e2269..c82fd64 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
@@ -43,6 +43,7 @@
 import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 import org.robolectric.Shadows
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
@@ -95,9 +96,10 @@
             sessionMode = CameraGraph.OperatingMode.HIGH_SPEED
         )
 
-    private val streamGraph = StreamGraphImpl(testCamera.metadata, graphConfig)
+    private val streamGraph = StreamGraphImpl(testCamera.metadata, graphConfig, mock())
 
-    private val highSpeedStreamGraph = StreamGraphImpl(testCamera.metadata, highSpeedGraphConfig)
+    private val highSpeedStreamGraph =
+        StreamGraphImpl(testCamera.metadata, highSpeedGraphConfig, mock())
 
     private val surface1 =
         Surface(
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
index 70cf322..12874ff 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
@@ -22,9 +22,11 @@
 import android.os.Looper
 import android.util.Size
 import android.view.Surface
+import androidx.camera.camera2.pipe.CameraController
 import androidx.camera.camera2.pipe.CameraExtensionMetadata
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.Flags.FinalizeSessionOnCloseBehavior
+import androidx.camera.camera2.pipe.CameraGraphId
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.CameraPipe
@@ -42,6 +44,7 @@
 import androidx.camera.camera2.pipe.core.SystemTimeSource
 import androidx.camera.camera2.pipe.graph.StreamGraphImpl
 import androidx.camera.camera2.pipe.internal.CameraErrorListener
+import androidx.camera.camera2.pipe.testing.FakeCameraController
 import androidx.camera.camera2.pipe.testing.FakeCaptureSequence
 import androidx.camera.camera2.pipe.testing.FakeCaptureSequenceProcessor
 import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
@@ -191,6 +194,13 @@
             streams = listOf(stream),
         )
     }
+
+    @Provides
+    @CameraGraphScope
+    fun provideFakeCameraController(): CameraController {
+        val graphId = CameraGraphId.nextId()
+        return FakeCameraController(graphId)
+    }
 }
 
 @Module
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index d507cda..5d975cc 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -59,6 +59,7 @@
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.eq
@@ -108,21 +109,15 @@
         )
     private val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
     private val graphLifecycleManager = GraphLifecycleManager(threads)
-    private val streamGraph = StreamGraphImpl(metadata, graphConfig)
     private val imageSources = ImageReaderImageSources(threads)
-    private val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, imageSources)
     private val frameCaptureQueue = FrameCaptureQueue()
+    private val cameraController =
+        CameraControllerSimulator(cameraContext, graphId, graphConfig, fakeGraphProcessor)
+    private val cameraControllerProvider: () -> CameraControllerSimulator = { cameraController }
+    private val streamGraph = StreamGraphImpl(metadata, graphConfig, cameraControllerProvider)
+    private val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, imageSources)
     private val frameDistributor =
         FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
-    private val cameraController =
-        CameraControllerSimulator(
-            cameraContext,
-            graphId,
-            graphConfig,
-            fakeGraphProcessor,
-            streamGraph
-        )
-
     private val surfaceGraph =
         SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
     private val audioRestriction = FakeAudioRestrictionController()
@@ -158,6 +153,11 @@
         cameraSurfaceManager.addListener(fakeSurfaceListener)
     }
 
+    @Before
+    fun setUp() {
+        cameraController.streamGraph = streamGraph
+    }
+
     @Test fun createCameraGraphImpl() = testScope.runTest { assertThat(cameraGraph).isNotNull() }
 
     @Test
@@ -468,4 +468,13 @@
         testScope.runTest {
             assertThrows<RuntimeException> { cameraGraph.useSession { throw RuntimeException() } }
         }
+
+    @Test
+    fun testGetOutputLatency() =
+        testScope.runTest {
+            assertThat(cameraController.getOutputLatency(null)).isNull()
+            cameraController.simulateOutputLatency()
+            assertThat(cameraController.getOutputLatency(null)?.estimatedLatencyNs)
+                .isEqualTo(cameraController.outputLatencySet?.estimatedLatencyNs)
+        }
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
index e9256d5..c142e13 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
@@ -16,9 +16,14 @@
 
 package androidx.camera.camera2.pipe.graph
 
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
+import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
 import android.os.Build
 import android.util.Size
+import androidx.camera.camera2.pipe.CameraBackendFactory
 import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraGraphId
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.CameraMetadata.Companion.isHardwareLevelExternal
@@ -27,9 +32,17 @@
 import androidx.camera.camera2.pipe.CameraStream
 import androidx.camera.camera2.pipe.OutputStream
 import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.internal.CameraBackendsImpl
+import androidx.camera.camera2.pipe.testing.CameraControllerSimulator
+import androidx.camera.camera2.pipe.testing.FakeCameraBackend
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.camera2.pipe.testing.FakeGraphConfigs
+import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
+import androidx.camera.camera2.pipe.testing.FakeThreads
 import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
@@ -39,11 +52,46 @@
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 internal class StreamGraphImplTest {
+    private val testScope = TestScope()
+
+    private val context = ApplicationProvider.getApplicationContext() as Context
+    private val metadata =
+        FakeCameraMetadata(
+            mapOf(INFO_SUPPORTED_HARDWARE_LEVEL to INFO_SUPPORTED_HARDWARE_LEVEL_FULL),
+        )
     private val config = FakeGraphConfigs
+    private val fakeGraphProcessor = FakeGraphProcessor()
+
+    private val stream1Config =
+        CameraStream.Config.create(Size(1280, 720), StreamFormat.YUV_420_888)
+    private val stream2Config =
+        CameraStream.Config.create(Size(1920, 1080), StreamFormat.YUV_420_888)
+
+    private val graphId = CameraGraphId.nextId()
+    private val graphConfig =
+        CameraGraph.Config(
+            camera = metadata.camera,
+            streams = listOf(stream1Config, stream2Config),
+        )
+    private val threads = FakeThreads.fromTestScope(testScope)
+    private val backend = FakeCameraBackend(fakeCameras = mapOf(metadata.camera to metadata))
+    private val backends =
+        CameraBackendsImpl(
+            defaultBackendId = backend.id,
+            cameraBackends = mapOf(backend.id to CameraBackendFactory { backend }),
+            context,
+            threads
+        )
+    private val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
+    private val cameraController =
+        CameraControllerSimulator(cameraContext, graphId, graphConfig, fakeGraphProcessor)
+    private val cameraControllerProvider: () -> CameraControllerSimulator = { cameraController }
 
     @Test
     fun testPrecomputedTestData() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         assertThat(streamGraph.streams).hasSize(10)
         assertThat(streamGraph.streams).hasSize(10)
@@ -75,7 +123,9 @@
 
     @Test
     fun testStreamGraphPopulatesCameraId() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream = streamGraph[config.streamConfig1]!!
         assertThat(config.streamConfig1.outputs.single().camera).isNull()
         assertThat(stream.outputs.single().camera).isEqualTo(config.graphConfig.camera)
@@ -96,7 +146,9 @@
                 camera = CameraId("0"),
                 streams = listOf(streamConfig),
             )
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         assertThat(streamGraph.streams).hasSize(1)
         assertThat(streamGraph.streams).hasSize(1)
@@ -129,7 +181,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -175,7 +229,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -216,7 +272,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -247,7 +305,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -294,7 +354,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -342,7 +404,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -388,7 +452,9 @@
                 streams = listOf(streamConfigA, streamConfigB, streamConfigC),
             )
 
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         // Get the stream for each streamConfig
         val streamA = streamGraph[streamConfigA]
@@ -409,7 +475,9 @@
 
     @Test
     fun testStreamMapConvertsConfigObjectsToStreamIds() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
 
         assertThat(streamGraph[config.streamConfig1]).isNotNull()
         assertThat(streamGraph[config.streamConfig2]).isNotNull()
@@ -430,8 +498,10 @@
 
     @Test
     fun testStreamMapIdsAreNotEqualAcrossMultipleStreamMapInstances() {
-        val streamGraphA = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
-        val streamGraphB = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraphA =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        val streamGraphB =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
 
         val stream1A = streamGraphA[config.streamConfig1]!!
         val stream1B = streamGraphB[config.streamConfig1]!!
@@ -442,7 +512,9 @@
 
     @Test
     fun testSharedStreamsHaveOneOutputConfig() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.sharedStreamConfig1]!!
         val stream2 = streamGraph[config.sharedStreamConfig2]!!
 
@@ -458,7 +530,9 @@
 
     @Test
     fun testSharedStreamsHaveDifferentOutputStreams() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.sharedStreamConfig1]!!
         val stream2 = streamGraph[config.sharedStreamConfig2]!!
 
@@ -467,7 +541,9 @@
 
     @Test
     fun testGroupedStreamsHaveSameGroupNumber() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         val stream2 = streamGraph[config.streamConfig2]!!
 
@@ -489,7 +565,9 @@
 
     @Test
     fun testDefaultAndPropagatedMirrorModes() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         assertThat(stream1.outputs.single().mirrorMode).isNull()
 
@@ -500,7 +578,9 @@
 
     @Test
     fun testDefaultAndPropagatedTimestampBases() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         assertThat(stream1.outputs.single().timestampBase).isNull()
 
@@ -511,7 +591,9 @@
 
     @Test
     fun testDefaultAndPropagatedDynamicRangeProfiles() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         assertThat(stream1.outputs.single().dynamicRangeProfile).isNull()
 
@@ -522,7 +604,9 @@
 
     @Test
     fun testDefaultAndPropagatedStreamUseCases() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         assertThat(stream1.outputs.single().streamUseCase).isNull()
 
@@ -533,7 +617,9 @@
 
     @Test
     fun testDefaultAndPropagatedStreamUseHints() {
-        val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
         val stream1 = streamGraph[config.streamConfig1]!!
         assertThat(stream1.outputs.single().streamUseCase).isNull()
 
@@ -542,6 +628,21 @@
             .isEqualTo(OutputStream.StreamUseHint.VIDEO_RECORD)
     }
 
+    @Test
+    fun testGetOutputLatency() {
+        val streamGraph =
+            StreamGraphImpl(config.fakeMetadata, config.graphConfig, cameraControllerProvider)
+        cameraController.streamGraph = streamGraph
+        val stream1 = streamGraph[config.streamConfig1]!!
+        assertThat(streamGraph.getOutputLatency(stream1.id)).isNull()
+        cameraController.simulateOutputLatency()
+        assertThat(
+            streamGraph
+                .getOutputLatency(stream1.id)
+                ?.equals(cameraController.outputLatencySet?.estimatedLatencyNs)
+        )
+    }
+
     private fun deferredStreamsAreSupported(
         cameraMetadata: CameraMetadata,
         graphConfig: CameraGraph.Config
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
index c1aac90..4f7c6c4 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
@@ -45,7 +45,7 @@
     private val graphId = CameraGraphId.nextId()
     private val fakeCameraController = FakeCameraController(graphId)
 
-    private val streamMap = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+    private val streamMap = StreamGraphImpl(config.fakeMetadata, config.graphConfig, mock())
 
     private val fakeSurfaceListener: CameraSurfaceManager.SurfaceListener = mock()
     private val cameraSurfaceManager =
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
index 4690516..0dbc0a5 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
@@ -21,6 +21,7 @@
 import androidx.camera.camera2.pipe.CameraGraphId
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraStatusMonitor
+import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
 
 internal class FakeCameraController(override val cameraGraphId: CameraGraphId) : CameraController {
@@ -53,4 +54,8 @@
     override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
         this.surfaceMap = surfaceMap
     }
+
+    override fun getOutputLatency(streamId: StreamId?): StreamGraph.OutputLatency? {
+        return null
+    }
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
index 0ecfcb6..f22eb33 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
@@ -113,12 +113,12 @@
         return true
     }
 
-    @RequiresApi(Build.VERSION_CODES.R)
+    @RequiresApi(30)
     override fun getCameraAudioRestriction(): AudioRestrictionMode {
         return AudioRestrictionMode(fakeCamera.cameraDevice.cameraAudioRestriction)
     }
 
-    @RequiresApi(Build.VERSION_CODES.R)
+    @RequiresApi(30)
     override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
         fakeCamera.cameraDevice.cameraAudioRestriction = mode.value
     }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt
index 8bf84b4..c11eab5 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/ImageSimulator.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.graph.StreamGraphImpl
 import androidx.camera.camera2.pipe.media.ImageSource
+import org.mockito.kotlin.mock
 
 class ImageSimulator(
     streamConfigs: List<CameraStream.Config>,
@@ -35,7 +36,7 @@
 
     val cameraMetadata = defaultCameraMetadata ?: FakeCameraMetadata()
     val graphConfig = CameraGraph.Config(camera = cameraMetadata.camera, streams = streamConfigs)
-    val streamGraph = defaultStreamGraph ?: StreamGraphImpl(cameraMetadata, graphConfig)
+    val streamGraph = defaultStreamGraph ?: StreamGraphImpl(cameraMetadata, graphConfig, mock())
 
     private val fakeImageSources = buildMap {
         for (config in graphConfig.streams) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/AndroidRZoomImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/AndroidRZoomImpl.java
index f9c6863..c30da9b 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/AndroidRZoomImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/AndroidRZoomImpl.java
@@ -27,6 +27,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.internal.compat.params.CaptureRequestParameterCompat;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.impl.Config;
@@ -41,11 +42,13 @@
     private float mCurrentZoomRatio = DEFAULT_ZOOM_RATIO;
     private CallbackToFutureAdapter.Completer<Void> mPendingZoomRatioCompleter;
     private float mPendingZoomRatio = 1.0f;
+    private boolean mShouldOverrideZoom = false;
 
     AndroidRZoomImpl(@NonNull CameraCharacteristicsCompat cameraCharacteristics) {
         mCameraCharacteristics = cameraCharacteristics;
         mZoomRatioRange = mCameraCharacteristics
                 .get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
+        mShouldOverrideZoom = mCameraCharacteristics.isZoomOverrideAvailable();
     }
 
     @Override
@@ -63,6 +66,10 @@
     public void addRequestOption(@NonNull Camera2ImplConfig.Builder builder) {
         builder.setCaptureRequestOptionWithPriority(CaptureRequest.CONTROL_ZOOM_RATIO,
                 mCurrentZoomRatio, Config.OptionPriority.REQUIRED);
+        if (mShouldOverrideZoom) {
+            CaptureRequestParameterCompat.setSettingsOverrideZoom(builder,
+                    Config.OptionPriority.REQUIRED);
+        }
     }
 
     @Override
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
index 15934dd..2333562 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.internal.compat;
 
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.os.Build;
 
@@ -72,11 +73,8 @@
      * caching it.
      */
     private boolean isKeyNonCacheable(@NonNull CameraCharacteristics.Key<?> key) {
-        // SENSOR_ORIENTATION value scould change in some circumstances.
-        if (key.equals(CameraCharacteristics.SENSOR_ORIENTATION)) {
-            return true;
-        }
-        return false;
+        // SENSOR_ORIENTATION value should change in some circumstances.
+        return key.equals(CameraCharacteristics.SENSOR_ORIENTATION);
     }
 
     /**
@@ -121,6 +119,24 @@
     }
 
     /**
+     * Returns {@code true} if overriding zoom setting is available, otherwise {@code false}.
+     */
+    public boolean isZoomOverrideAvailable() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            int[] availableSettingsOverrides = mCameraCharacteristicsImpl.get(
+                    CameraCharacteristics.CONTROL_AVAILABLE_SETTINGS_OVERRIDES);
+            if (availableSettingsOverrides != null) {
+                for (int i : availableSettingsOverrides) {
+                    if (i == CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
      * Obtains the {@link StreamConfigurationMapCompat} which contains the output sizes related
      * workarounds in it.
      */
@@ -170,13 +186,13 @@
      */
     public interface CameraCharacteristicsCompatImpl {
         /**
-         * Gets the key/values from the CameraCharacteristics .
+         * Gets the key/values from the CameraCharacteristics.
          */
         @Nullable
         <T> T get(@NonNull CameraCharacteristics.Key<T> key);
 
         /**
-         * Get physical camera ids.
+         * Gets physical camera ids.
          */
         @NonNull
         Set<String> getPhysicalCameraIds();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/CaptureRequestParameterCompat.kt b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/CaptureRequestParameterCompat.kt
new file mode 100644
index 0000000..6b55e40
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/CaptureRequestParameterCompat.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.params
+
+import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.annotation.OptIn
+import androidx.camera.camera2.impl.Camera2ImplConfig
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.core.impl.Config.OptionPriority
+
+/** Helper for accessing features in [CaptureRequest] in a backwards compatible fashion. */
+internal object CaptureRequestParameterCompat {
+    /** Sets the [CaptureRequest.CONTROL_SETTINGS_OVERRIDE_ZOOM] option if supported. */
+    @OptIn(ExperimentalCamera2Interop::class)
+    @JvmStatic
+    fun setSettingsOverrideZoom(options: Camera2ImplConfig.Builder, priority: OptionPriority) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            options.setCaptureRequestOptionWithPriority(
+                CaptureRequest.CONTROL_SETTINGS_OVERRIDE,
+                CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM,
+                priority
+            )
+        }
+    }
+}
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index be07f31..ffd3d525 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -64,9 +64,10 @@
   }
 
   public interface CameraProvider {
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method @SuppressCompatibility @androidx.camera.core.ExperimentalCameraInfo public default androidx.camera.core.CameraInfo getCameraInfo(androidx.camera.core.CameraSelector);
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    method public java.util.List<androidx.camera.core.CameraInfo> getAvailableCameraInfos();
+    method public default androidx.camera.core.CameraInfo getCameraInfo(androidx.camera.core.CameraSelector cameraSelector);
+    method @kotlin.jvm.Throws(exceptionClasses=CameraInfoUnavailableException::class) public boolean hasCamera(androidx.camera.core.CameraSelector cameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    property public abstract java.util.List<androidx.camera.core.CameraInfo> availableCameraInfos;
   }
 
   public final class CameraSelector {
@@ -204,9 +205,6 @@
     field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
   }
 
-  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraInfo {
-  }
-
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index be07f31..ffd3d525 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -64,9 +64,10 @@
   }
 
   public interface CameraProvider {
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method @SuppressCompatibility @androidx.camera.core.ExperimentalCameraInfo public default androidx.camera.core.CameraInfo getCameraInfo(androidx.camera.core.CameraSelector);
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    method public java.util.List<androidx.camera.core.CameraInfo> getAvailableCameraInfos();
+    method public default androidx.camera.core.CameraInfo getCameraInfo(androidx.camera.core.CameraSelector cameraSelector);
+    method @kotlin.jvm.Throws(exceptionClasses=CameraInfoUnavailableException::class) public boolean hasCamera(androidx.camera.core.CameraSelector cameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    property public abstract java.util.List<androidx.camera.core.CameraInfo> availableCameraInfos;
   }
 
   public final class CameraSelector {
@@ -204,9 +205,6 @@
     field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
   }
 
-  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraInfo {
-  }
-
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
deleted file mode 100644
index 2a9191b..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import androidx.annotation.NonNull;
-
-import java.util.List;
-
-/**
- * A {@link CameraProvider} provides basic access to a set of cameras such as querying for camera
- * existence or information.
- *
- * <p>A device might have multiple cameras. According to the applications' design, they might
- * need to search for a suitable camera which supports their functions. A {@link CameraProvider}
- * allows the applications to check whether any camera exists to fulfill the requirements or to
- * get {@link CameraInfo} instances of all cameras to retrieve the camera information.
- */
-public interface CameraProvider {
-
-    /**
-     * Checks whether this provider supports at least one camera that meets the requirements from a
-     * {@link CameraSelector}.
-     *
-     * <p>If this method returns {@code true}, then the camera selector can be used to bind
-     * use cases and retrieve a {@link Camera} instance.
-     *
-     * @param cameraSelector the {@link CameraSelector} that filters available cameras.
-     * @return true if the device has at least one available camera, otherwise false.
-     * @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
-     *                                        insufficient permissions.
-     */
-    boolean hasCamera(@NonNull CameraSelector cameraSelector) throws CameraInfoUnavailableException;
-
-    /**
-     * Returns {@link CameraInfo} instances of the available cameras.
-     *
-     * <p>While iterating through all the available {@link CameraInfo}, if one of them meets some
-     * predefined requirements, a {@link CameraSelector} that uniquely identifies its camera
-     * can be retrieved using {@link CameraInfo#getCameraSelector()}, which can then be used to bind
-     * {@linkplain UseCase use cases} to that camera.
-     *
-     * @return A list of {@link CameraInfo} instances for the available cameras.
-     */
-    @NonNull
-    List<CameraInfo> getAvailableCameraInfos();
-
-    /**
-     * Returns the {@link CameraInfo} instance of the camera resulted from the specified
-     * {@link CameraSelector}.
-     *
-     * <p>The returned {@link CameraInfo} corresponds to the camera that will be bound when calling
-     * {@code bindToLifecycle} with the specified {@link CameraSelector}.
-     *
-     * @param cameraSelector the {@link CameraSelector} to use for selecting the camera to receive
-     * information about.
-     * @return the corresponding {@link CameraInfo}.
-     * @throws IllegalArgumentException if the given {@link CameraSelector} can't result in a
-     * valid camera to provide the {@link CameraInfo}.
-     */
-    @ExperimentalCameraInfo
-    @NonNull
-    default CameraInfo getCameraInfo(@NonNull CameraSelector cameraSelector) {
-        throw new UnsupportedOperationException("The camera provider is not implemented properly.");
-    }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt
new file mode 100644
index 0000000..94fa6ae
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.core
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * A [CameraProvider] provides basic access to a set of cameras such as querying for camera
+ * existence or information.
+ *
+ * A device might have multiple cameras. According to the applications' design, they might need to
+ * search for a suitable camera which supports their functions. A [CameraProvider] allows the
+ * applications to check whether any camera exists to fulfill the requirements or to get
+ * [CameraInfo] instances of all cameras to retrieve the camera information.
+ */
+public interface CameraProvider {
+    /**
+     * The [CameraInfo] instances of the available cameras.
+     *
+     * While iterating through all the available [CameraInfo], if one of them meets some predefined
+     * requirements, a [CameraSelector] that uniquely identifies its camera can be retrieved using
+     * [CameraInfo.getCameraSelector], which can then be used to bind [use cases][UseCase] to that
+     * camera.
+     */
+    public val availableCameraInfos: List<CameraInfo>
+
+    /**
+     * Returns list of [CameraInfo] instances of the available concurrent cameras.
+     *
+     * The available concurrent cameras include all combinations of cameras which could operate
+     * concurrently on the device. Each list maps to one combination of these camera's [CameraInfo].
+     *
+     * For example, to select a front camera and a back camera and bind to [LifecycleOwner] with
+     * preview [UseCase], this function could be used with `bindToLifecycle`.
+     *
+     * @sample androidx.camera.lifecycle.samples.bindConcurrentCameraSample
+     * @return List of combinations of [CameraInfo].
+     */
+    @get:RestrictTo(Scope.LIBRARY_GROUP)
+    public val availableConcurrentCameraInfos: List<List<CameraInfo>>
+
+    /**
+     * Returns whether there is a [ConcurrentCamera] bound.
+     *
+     * @return `true` if there is a [ConcurrentCamera] bound, otherwise `false`.
+     */
+    @get:RestrictTo(Scope.LIBRARY_GROUP) public val isConcurrentCameraModeOn: Boolean
+
+    /**
+     * Checks whether this provider supports at least one camera that meets the requirements from a
+     * [CameraSelector].
+     *
+     * If this method returns `true`, then the camera selector can be used to bind use cases and
+     * retrieve a [Camera] instance.
+     *
+     * @param cameraSelector the [CameraSelector] that filters available cameras.
+     * @return `true` if the device has at least one available camera, otherwise `false`.
+     * @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
+     *   insufficient permissions.
+     */
+    @Throws(CameraInfoUnavailableException::class)
+    public fun hasCamera(cameraSelector: CameraSelector): Boolean
+
+    /**
+     * Returns the [CameraInfo] instance of the camera resulted from the specified [CameraSelector].
+     *
+     * The returned [CameraInfo] corresponds to the camera that will be bound when calling
+     * `bindToLifecycle` with the specified [CameraSelector].
+     *
+     * @param cameraSelector the [CameraSelector] to use for selecting the camera to receive
+     *   information about.
+     * @return the corresponding [CameraInfo].
+     * @throws IllegalArgumentException if the given [CameraSelector] can't result in a valid camera
+     *   to provide the [CameraInfo].
+     */
+    public fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo {
+        throw UnsupportedOperationException("The camera provider is not implemented properly.")
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 1725729..541a169 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -86,6 +86,7 @@
 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType;
 import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.internal.compat.workaround.StreamSharingForceEnabler;
 import androidx.camera.core.streamsharing.StreamSharing;
 import androidx.core.util.Preconditions;
 
@@ -176,6 +177,8 @@
     private final CompositionSettings mCompositionSettings;
     @NonNull
     private final CompositionSettings mSecondaryCompositionSettings;
+    private final StreamSharingForceEnabler mStreamSharingForceEnabler =
+            new StreamSharingForceEnabler();
 
     /**
      * Create a new {@link CameraUseCaseAdapter} instance.
@@ -359,7 +362,7 @@
             // Force enable StreamSharing for Extensions to support VideoCapture. This means that
             // applyStreamSharing is set to true when the use case combination contains
             // VideoCapture and Extensions is enabled.
-            if (!applyStreamSharing && hasExtension() && hasVideoCapture(appUseCases)) {
+            if (!applyStreamSharing && shouldForceEnableStreamSharing(appUseCases)) {
                 updateUseCases(appUseCases, /*applyStreamSharing*/true, isDualCamera);
                 return;
             }
@@ -508,6 +511,15 @@
         }
     }
 
+    private boolean shouldForceEnableStreamSharing(@NonNull Collection<UseCase> appUseCases) {
+        if (hasExtension() && hasVideoCapture(appUseCases)) {
+            return true;
+        }
+
+        return mStreamSharingForceEnabler.shouldForceEnableStreamSharing(
+                mCameraInternal.getCameraInfoInternal().getCameraId(), appUseCases);
+    }
+
     /**
      * Return true if the given StreamSpec has any option with a different value than that
      * of the given sessionConfig.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
index 86a9be5..86948ad 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
@@ -63,6 +63,10 @@
                 IncorrectJpegMetadataQuirk.load())) {
             quirks.add(new IncorrectJpegMetadataQuirk());
         }
+        if (quirkSettings.shouldEnableQuirk(ImageCaptureFailedForSpecificCombinationQuirk.class,
+                ImageCaptureFailedForSpecificCombinationQuirk.load())) {
+            quirks.add(new ImageCaptureFailedForSpecificCombinationQuirk());
+        }
 
         return quirks;
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureFailedForSpecificCombinationQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureFailedForSpecificCombinationQuirk.java
new file mode 100644
index 0000000..d468b9f
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureFailedForSpecificCombinationQuirk.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal.compat.quirk;
+
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_TYPE;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.Preview;
+import androidx.camera.core.UseCase;
+import androidx.camera.core.impl.Quirk;
+import androidx.camera.core.impl.UseCaseConfigFactory;
+
+import java.util.Collection;
+
+/**
+ * <p>QuirkSummary
+ *     Bug Id: 359062845
+ *     Description: Quirk required to check whether still image capture can run failed when a
+ *     specific combination of UseCases are bound together.
+ *     Device(s): OnePlus 12
+ */
+public final class ImageCaptureFailedForSpecificCombinationQuirk implements Quirk {
+    static boolean load() {
+        return isOnePlus12();
+    }
+
+    private static boolean isOnePlus12() {
+        return "oneplus".equalsIgnoreCase(Build.BRAND) && "cph2583".equalsIgnoreCase(Build.MODEL);
+    }
+
+    /**
+     *  Returns whether stream sharing should be forced enabled for specific camera and UseCase
+     *  combination.
+     */
+    public boolean shouldForceEnableStreamSharing(@NonNull String cameraId,
+            @NonNull Collection<UseCase> appUseCases) {
+        if (isOnePlus12()) {
+            return shouldForceEnableStreamSharingForOnePlus12(cameraId, appUseCases);
+        }
+        return false;
+    }
+
+    /**
+     * On OnePlus 12, still image capture run failed on the front camera only when the UseCase
+     * combination is exactly Preview + VideoCapture + ImageCapture.
+     */
+    private boolean shouldForceEnableStreamSharingForOnePlus12(@NonNull String cameraId,
+            @NonNull Collection<UseCase> appUseCases) {
+        if (!cameraId.equals("1") || appUseCases.size() != 3) {
+            return false;
+        }
+
+        boolean hasPreview = false;
+        boolean hasVideoCapture = false;
+        boolean hasImageCapture = false;
+
+        for (UseCase useCase : appUseCases) {
+            if (useCase instanceof Preview) {
+                hasPreview = true;
+            } else if (useCase instanceof ImageCapture) {
+                hasImageCapture = true;
+            } else {
+                if (useCase.getCurrentConfig().containsOption(OPTION_CAPTURE_TYPE)) {
+                    hasVideoCapture = useCase.getCurrentConfig().getCaptureType()
+                            == UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE;
+                }
+            }
+        }
+
+        return hasPreview && hasVideoCapture && hasImageCapture;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnabler.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnabler.java
new file mode 100644
index 0000000..21209cb
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnabler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal.compat.workaround;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.UseCase;
+import androidx.camera.core.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.core.internal.compat.quirk.ImageCaptureFailedForSpecificCombinationQuirk;
+
+import java.util.Collection;
+
+/**
+ * Workaround to check whether stream sharing should be forced enabled.
+ *
+ * @see ImageCaptureFailedForSpecificCombinationQuirk
+ */
+public class StreamSharingForceEnabler {
+    @Nullable
+    private final ImageCaptureFailedForSpecificCombinationQuirk mSpecificCombinationQuirk =
+            DeviceQuirks.get(ImageCaptureFailedForSpecificCombinationQuirk.class);
+
+    /**
+     * Returns whether stream sharing should be forced enabled.
+     */
+    public boolean shouldForceEnableStreamSharing(@NonNull String cameraId,
+            @NonNull Collection<UseCase> appUseCases) {
+        if (mSpecificCombinationQuirk != null) {
+            return mSpecificCombinationQuirk.shouldForceEnableStreamSharing(cameraId, appUseCases);
+        }
+
+        return false;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
index 1cdc173..3820ac5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
@@ -37,6 +37,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 import androidx.arch.core.util.Function;
+import androidx.camera.core.CameraXThreads;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.Logger;
 import androidx.camera.core.SurfaceOutput;
@@ -110,7 +111,7 @@
      */
     DefaultSurfaceProcessor(@NonNull DynamicRange dynamicRange,
             @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides) {
-        mGlThread = new HandlerThread("GL Thread");
+        mGlThread = new HandlerThread(CameraXThreads.TAG + "GL Thread");
         mGlThread.start();
         mGlHandler = new Handler(mGlThread.getLooper());
         mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
index 3f5be70..443b757 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
@@ -27,6 +27,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
+import androidx.camera.core.CameraXThreads;
 import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.Logger;
@@ -92,7 +93,7 @@
             @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides,
             @NonNull CompositionSettings primaryCompositionSettings,
             @NonNull CompositionSettings secondaryCompositionSettings) {
-        mGlThread = new HandlerThread("GL Thread");
+        mGlThread = new HandlerThread(CameraXThreads.TAG + "GL Thread");
         mGlThread.start();
         mGlHandler = new Handler(mGlThread.getLooper());
         mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnablerTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnablerTest.kt
new file mode 100644
index 0000000..e7e0ee7
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/StreamSharingForceEnablerTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal.compat.workaround
+
+import android.os.Build
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
+
+private const val PREVIEW = 0x1
+private const val IMAGE_CAPTURE = 0x2
+private const val VIDEO_CAPTURE = 0x4
+private const val IMAGE_ANALYSIS = 0x8
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class StreamSharingForceEnablerTest(
+    private val brand: String,
+    private val model: String,
+    private val cameraId: String,
+    private val useCaseCombination: Int,
+    private val shouldEnableStreamSharing: Boolean
+) {
+    companion object {
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(
+            name = "brand={0}, model={1}, cameraId={2}, useCases={3}, result={4}"
+        )
+        fun data() =
+            mutableListOf<Array<Any?>>().apply {
+                add(
+                    arrayOf(
+                        "OnePlus",
+                        "cph2583",
+                        "0",
+                        PREVIEW or IMAGE_CAPTURE or VIDEO_CAPTURE,
+                        false
+                    )
+                )
+                add(
+                    arrayOf(
+                        "OnePlus",
+                        "cph2583",
+                        "1",
+                        PREVIEW or IMAGE_CAPTURE or VIDEO_CAPTURE,
+                        true
+                    )
+                )
+                add(
+                    arrayOf(
+                        "OnePlus",
+                        "cph2583",
+                        "1",
+                        PREVIEW or IMAGE_CAPTURE or VIDEO_CAPTURE or IMAGE_ANALYSIS,
+                        false
+                    )
+                )
+                add(arrayOf("", "", "1", PREVIEW or IMAGE_CAPTURE or VIDEO_CAPTURE, false))
+            }
+    }
+
+    @Test
+    fun shouldForceEnableStreamSharing() {
+        ReflectionHelpers.setStaticField(Build::class.java, "BRAND", brand)
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", model)
+
+        assertThat(
+                StreamSharingForceEnabler()
+                    .shouldForceEnableStreamSharing(cameraId, createUseCases(useCaseCombination))
+            )
+            .isEqualTo(shouldEnableStreamSharing)
+    }
+
+    private fun createUseCases(useCaseCombination: Int): Collection<UseCase> {
+        val useCases = mutableListOf<UseCase>()
+
+        if (useCaseCombination and PREVIEW != 0) {
+            useCases.add(Preview.Builder().build())
+        }
+        if (useCaseCombination and IMAGE_CAPTURE != 0) {
+            useCases.add(ImageCapture.Builder().build())
+        }
+        if (useCaseCombination and VIDEO_CAPTURE != 0) {
+            useCases.add(
+                FakeUseCaseConfig.Builder()
+                    .setCaptureType(UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE)
+                    .build()
+            )
+        }
+        if (useCaseCombination and IMAGE_ANALYSIS != 0) {
+            useCases.add(ImageAnalysis.Builder().build())
+        }
+
+        return useCases
+    }
+}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
index da4a830..d7e3251 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -244,7 +244,7 @@
      * {@link CameraCharacteristics#CONTROL_VIDEO_STABILIZATION_MODE_OFF} for the key
      * {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES}.
      *
-     * <p>Please note that on Android 15 or later, it is mandatory to include
+     * <p>Please note that it is mandatory to include
      * {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE} and
      * {@link CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES} in the list.
      *
diff --git a/camera/camera-lifecycle/api/current.txt b/camera/camera-lifecycle/api/current.txt
index a73d56a..575c094 100644
--- a/camera/camera-lifecycle/api/current.txt
+++ b/camera/camera-lifecycle/api/current.txt
@@ -18,6 +18,7 @@
     method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> shutdownAsync();
     method @MainThread public void unbind(androidx.camera.core.UseCase?... useCases);
     method @MainThread public void unbindAll();
+    property public java.util.List<androidx.camera.core.CameraInfo> availableCameraInfos;
     property public final java.util.List<java.util.List<androidx.camera.core.CameraInfo>> availableConcurrentCameraInfos;
     property @MainThread public final boolean isConcurrentCameraModeOn;
     field public static final androidx.camera.lifecycle.ProcessCameraProvider.Companion Companion;
diff --git a/camera/camera-lifecycle/api/restricted_current.txt b/camera/camera-lifecycle/api/restricted_current.txt
index a73d56a..575c094 100644
--- a/camera/camera-lifecycle/api/restricted_current.txt
+++ b/camera/camera-lifecycle/api/restricted_current.txt
@@ -18,6 +18,7 @@
     method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> shutdownAsync();
     method @MainThread public void unbind(androidx.camera.core.UseCase?... useCases);
     method @MainThread public void unbindAll();
+    property public java.util.List<androidx.camera.core.CameraInfo> availableCameraInfos;
     property public final java.util.List<java.util.List<androidx.camera.core.CameraInfo>> availableConcurrentCameraInfos;
     property @MainThread public final boolean isConcurrentCameraModeOn;
     field public static final androidx.camera.lifecycle.ProcessCameraProvider.Companion Companion;
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.java
deleted file mode 100644
index f106946..0000000
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.lifecycle;
-
-import androidx.annotation.NonNull;
-import androidx.camera.core.CameraProvider;
-import androidx.camera.core.UseCase;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-
-/**
- * Provides access to a camera which has has its opening and closing controlled by a
- * {@link LifecycleOwner}.
- */
-interface LifecycleCameraProvider extends CameraProvider {
-
-    /**
-     * Returns true if the {@link UseCase} is bound to a lifecycle. Otherwise returns false.
-     *
-     * <p>After binding a use case, use cases remain bound until the lifecycle reaches a
-     * {@link Lifecycle.State#DESTROYED} state or if is unbound by calls to
-     * {@link #unbind(UseCase...)} or {@link #unbindAll()}.
-     */
-    boolean isBound(@NonNull UseCase useCase);
-
-    /**
-     * Unbinds all specified use cases from the lifecycle provider.
-     *
-     * <p>This will initiate a close of every open camera which has zero {@link UseCase}
-     * associated with it at the end of this call.
-     *
-     * <p>If a use case in the argument list is not bound, then it is simply ignored.
-     *
-     * <p>After unbinding a UseCase, the UseCase can be bound to another {@link Lifecycle}
-     * however listeners and settings should be reset by the application.
-     *
-     * @param useCases The collection of use cases to remove.
-     * @throws IllegalStateException If not called on main thread.
-     */
-    void unbind(@NonNull UseCase... useCases);
-
-    /**
-     * Unbinds all use cases from the lifecycle provider and removes them from CameraX.
-     *
-     * <p>This will initiate a close of every currently open camera.
-     *
-     * @throws IllegalStateException If not called on main thread.
-     */
-    void unbindAll();
-}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt
new file mode 100644
index 0000000..33cc057
--- /dev/null
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.lifecycle
+
+import android.content.pm.PackageManager
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraProvider
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CompositionSettings
+import androidx.camera.core.ConcurrentCamera
+import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
+import androidx.camera.core.UseCaseGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * Provides access to a camera which has has its opening and closing controlled by a
+ * [LifecycleOwner].
+ */
+internal interface LifecycleCameraProvider : CameraProvider {
+    /**
+     * Returns `true` if the [UseCase] is bound to a lifecycle. Otherwise returns `false`.
+     *
+     * After binding a use case, use cases remain bound until the lifecycle reaches a
+     * [Lifecycle.State.DESTROYED] state or if is unbound by calls to [unbind] or [unbindAll].
+     */
+    fun isBound(useCase: UseCase): Boolean
+
+    /**
+     * Unbinds all specified use cases from the lifecycle provider.
+     *
+     * This will initiate a close of every open camera which has zero [UseCase] associated with it
+     * at the end of this call.
+     *
+     * If a use case in the argument list is not bound, then it is simply ignored.
+     *
+     * After unbinding a UseCase, the UseCase can be bound to another [Lifecycle] however listeners
+     * and settings should be reset by the application.
+     *
+     * @param useCases The collection of use cases to remove.
+     * @throws IllegalStateException If not called on main thread.
+     */
+    fun unbind(vararg useCases: UseCase?)
+
+    /**
+     * Unbinds all use cases from the lifecycle provider and removes them from CameraX.
+     *
+     * This will initiate a close of every currently open camera.
+     *
+     * @throws IllegalStateException If not called on main thread.
+     */
+    fun unbindAll()
+
+    /**
+     * Binds the collection of [UseCase] to a [LifecycleOwner].
+     *
+     * The state of the lifecycle will determine when the cameras are open, started, stopped and
+     * closed. When started, the use cases receive camera data.
+     *
+     * Binding to a lifecycleOwner in state currently in [Lifecycle.State.STARTED] or greater will
+     * also initialize and start data capture. If the camera was already running this may cause a
+     * new initialization to occur temporarily stopping data from the camera before restarting it.
+     *
+     * Multiple use cases can be bound via adding them all to a single bindToLifecycle call, or by
+     * using multiple bindToLifecycle calls. Using a single call that includes all the use cases
+     * helps to set up a camera session correctly for all uses cases, such as by allowing
+     * determination of resolutions depending on all the use cases bound being bound. If the use
+     * cases are bound separately, it will find the supported resolution with the priority depending
+     * on the binding sequence. If the use cases are bound with a single call, it will find the
+     * supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
+     * [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
+     * level that there are some default guaranteed resolutions listed in
+     * [android.hardware.camera2.CameraDevice.createCaptureSession].
+     *
+     * Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
+     * of target camera device will throw an IllegalArgumentException.
+     *
+     * A UseCase should only be bound to a single lifecycle and camera selector a time. Attempting
+     * to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
+     * and the use case binding will not change. Attempting to bind the same use case to multiple
+     * camera selectors is also an error and will not change the binding.
+     *
+     * If different use cases are bound to different camera selectors that resolve to distinct
+     * cameras, but the same lifecycle, only one of the cameras will operate at a time. The
+     * non-operating camera will not become active until it is the only camera with use cases bound.
+     *
+     * The [Camera] returned is determined by the given camera selector, plus other internal
+     * requirements, possibly from use case configurations. The camera returned from bindToLifecycle
+     * may differ from the camera determined solely by a camera selector. If the camera selector
+     * can't resolve a valid camera under the requirements, an IllegalArgumentException will be
+     * thrown.
+     *
+     * Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
+     * [Lifecycle] will be stopped.
+     *
+     * @param lifecycleOwner The lifecycleOwner which controls the lifecycle transitions of the use
+     *   cases.
+     * @param cameraSelector The camera selector which determines the camera to use for set of use
+     *   cases.
+     * @param useCases The use cases to bind to a lifecycle.
+     * @return The [Camera] instance which is determined by the camera selector and internal
+     *   requirements.
+     * @throws IllegalStateException If the use case has already been bound to another lifecycle or
+     *   method is not called on main thread.
+     * @throws IllegalArgumentException If the provided camera selector is unable to resolve a
+     *   camera to be used for the given use cases.
+     * @throws UnsupportedOperationException If the camera is configured in concurrent mode.
+     */
+    fun bindToLifecycle(
+        lifecycleOwner: LifecycleOwner,
+        cameraSelector: CameraSelector,
+        vararg useCases: UseCase?
+    ): Camera
+
+    /**
+     * Binds a [UseCaseGroup] to a [LifecycleOwner].
+     *
+     * Similar to [bindToLifecycle], with the addition that the bound collection of [UseCase] share
+     * parameters defined by [UseCaseGroup] such as consistent camera sensor rect across all
+     * [UseCase]s.
+     *
+     * If one [UseCase] is in multiple [UseCaseGroup]s, it will be linked to the [UseCaseGroup] in
+     * the latest [bindToLifecycle] call.
+     *
+     * @throws UnsupportedOperationException If the camera is configured in concurrent mode.
+     */
+    fun bindToLifecycle(
+        lifecycleOwner: LifecycleOwner,
+        cameraSelector: CameraSelector,
+        useCaseGroup: UseCaseGroup
+    ): Camera
+
+    /**
+     * Binds list of [SingleCameraConfig]s to [LifecycleOwner].
+     *
+     * The concurrent camera is only supporting two cameras currently. If the input list of
+     * [SingleCameraConfig]s have less or more than two [SingleCameraConfig]s,
+     * [IllegalArgumentException] will be thrown. If cameras are already used by other [UseCase]s,
+     * [UnsupportedOperationException] will be thrown.
+     *
+     * A logical camera is a grouping of two or more of those physical cameras. See
+     * [Multi-camera API](https://developer.android.com/media/camera/camera2/multi-camera)
+     *
+     * If we want to open concurrent logical cameras, which are one front camera and one back
+     * camera, the device needs to support [PackageManager.FEATURE_CAMERA_CONCURRENT]. To set up
+     * concurrent logical camera, call [availableConcurrentCameraInfos] to get the list of available
+     * combinations of concurrent cameras. Each sub-list contains the [CameraInfo]s for a
+     * combination of cameras that can be operated concurrently. Each logical camera can have its
+     * own [UseCase]s and [LifecycleOwner]. See
+     * [CameraX lifecycles]({@docRoot}training/camerax/architecture#lifecycles)
+     *
+     * If the concurrent logical cameras are binding the same preview and video capture use cases,
+     * the concurrent cameras video recording will be supported. The concurrent camera preview
+     * stream will be shared with video capture and record the concurrent cameras as a whole. The
+     * [CompositionSettings] can be used to configure the position of each camera stream.
+     *
+     * If we want to open concurrent physical cameras, which are two front cameras or two back
+     * cameras, the device needs to support physical cameras and the capability could be checked via
+     * [CameraInfo.isLogicalMultiCameraSupported]. Each physical cameras can have its own [UseCase]s
+     * but needs to have the same [LifecycleOwner], otherwise [IllegalArgumentException] will be
+     * thrown.
+     *
+     * If we want to open one physical camera, for example ultra wide, we just need to set physical
+     * camera id in [CameraSelector] and bind to lifecycle. All CameraX features will work normally
+     * when only a single physical camera is used.
+     *
+     * If we want to open multiple physical cameras, we need to have multiple [CameraSelector]s,
+     * each in one [SingleCameraConfig] and set physical camera id, then bind to lifecycle with the
+     * [SingleCameraConfig]s. Internally each physical camera id will be set on [UseCase], for
+     * example, [Preview] and call
+     * [android.hardware.camera2.params.OutputConfiguration.setPhysicalCameraId].
+     *
+     * Currently only two physical cameras for the same logical camera id are allowed and the device
+     * needs to support physical cameras by checking [CameraInfo.isLogicalMultiCameraSupported]. In
+     * addition, there is no guarantee or API to query whether the device supports multiple physical
+     * camera opening or not. Internally the library checks
+     * [android.hardware.camera2.CameraDevice.isSessionConfigurationSupported], if the device does
+     * not support the multiple physical camera configuration, [IllegalArgumentException] will be
+     * thrown.
+     *
+     * @param singleCameraConfigs Input list of [SingleCameraConfig]s.
+     * @return Output [ConcurrentCamera] instance.
+     * @throws IllegalArgumentException If less or more than two camera configs are provided.
+     * @throws UnsupportedOperationException If device is not supporting concurrent camera or
+     *   cameras are already used by other [UseCase]s.
+     * @see ConcurrentCamera
+     * @see availableConcurrentCameraInfos
+     * @see CameraInfo.isLogicalMultiCameraSupported
+     * @see CameraInfo.getPhysicalCameraInfos
+     */
+    fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera
+}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
index 259b94e..eb30744 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
@@ -18,11 +18,9 @@
 
 import android.app.Application
 import android.content.Context
-import android.content.pm.PackageManager
 import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
 import androidx.annotation.GuardedBy
 import androidx.annotation.MainThread
-import androidx.annotation.OptIn
 import androidx.annotation.VisibleForTesting
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraEffect
@@ -35,7 +33,6 @@
 import androidx.camera.core.CompositionSettings
 import androidx.camera.core.ConcurrentCamera
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
-import androidx.camera.core.ExperimentalCameraInfo
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.InitializationException
@@ -140,63 +137,8 @@
         return shutdownFuture
     }
 
-    /**
-     * Binds the collection of [UseCase] to a [LifecycleOwner].
-     *
-     * The state of the lifecycle will determine when the cameras are open, started, stopped and
-     * closed. When started, the use cases receive camera data.
-     *
-     * Binding to a lifecycleOwner in state currently in [Lifecycle.State.STARTED] or greater will
-     * also initialize and start data capture. If the camera was already running this may cause a
-     * new initialization to occur temporarily stopping data from the camera before restarting it.
-     *
-     * Multiple use cases can be bound via adding them all to a single bindToLifecycle call, or by
-     * using multiple bindToLifecycle calls. Using a single call that includes all the use cases
-     * helps to set up a camera session correctly for all uses cases, such as by allowing
-     * determination of resolutions depending on all the use cases bound being bound. If the use
-     * cases are bound separately, it will find the supported resolution with the priority depending
-     * on the binding sequence. If the use cases are bound with a single call, it will find the
-     * supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
-     * [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
-     * level that there are some default guaranteed resolutions listed in
-     * [android.hardware.camera2.CameraDevice.createCaptureSession].
-     *
-     * Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
-     * of target camera device will throw an IllegalArgumentException.
-     *
-     * A UseCase should only be bound to a single lifecycle and camera selector a time. Attempting
-     * to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
-     * and the use case binding will not change. Attempting to bind the same use case to multiple
-     * camera selectors is also an error and will not change the binding.
-     *
-     * If different use cases are bound to different camera selectors that resolve to distinct
-     * cameras, but the same lifecycle, only one of the cameras will operate at a time. The
-     * non-operating camera will not become active until it is the only camera with use cases bound.
-     *
-     * The [Camera] returned is determined by the given camera selector, plus other internal
-     * requirements, possibly from use case configurations. The camera returned from bindToLifecycle
-     * may differ from the camera determined solely by a camera selector. If the camera selector
-     * can't resolve a valid camera under the requirements, an IllegalArgumentException will be
-     * thrown.
-     *
-     * Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
-     * [Lifecycle] will be stopped.
-     *
-     * @param lifecycleOwner The lifecycleOwner which controls the lifecycle transitions of the use
-     *   cases.
-     * @param cameraSelector The camera selector which determines the camera to use for set of use
-     *   cases.
-     * @param useCases The use cases to bind to a lifecycle.
-     * @return The [Camera] instance which is determined by the camera selector and internal
-     *   requirements.
-     * @throws IllegalStateException If the use case has already been bound to another lifecycle or
-     *   method is not called on main thread.
-     * @throws IllegalArgumentException If the provided camera selector is unable to resolve a
-     *   camera to be used for the given use cases.
-     * @throws UnsupportedOperationException If the camera is configured in concurrent mode.
-     */
     @MainThread
-    public fun bindToLifecycle(
+    public override fun bindToLifecycle(
         lifecycleOwner: LifecycleOwner,
         cameraSelector: CameraSelector,
         vararg useCases: UseCase?
@@ -236,7 +178,7 @@
      * @throws UnsupportedOperationException If the camera is configured in concurrent mode.
      */
     @MainThread
-    public fun bindToLifecycle(
+    public override fun bindToLifecycle(
         lifecycleOwner: LifecycleOwner,
         cameraSelector: CameraSelector,
         useCaseGroup: UseCaseGroup
@@ -263,67 +205,10 @@
             return@trace camera
         }
 
-    /**
-     * Binds list of [SingleCameraConfig]s to [LifecycleOwner].
-     *
-     * The concurrent camera is only supporting two cameras currently. If the input list of
-     * [SingleCameraConfig]s have less or more than two [SingleCameraConfig]s,
-     * [IllegalArgumentException] will be thrown. If cameras are already used by other [UseCase]s,
-     * [UnsupportedOperationException] will be thrown.
-     *
-     * A logical camera is a grouping of two or more of those physical cameras. See
-     * [Multi-camera API](https://developer.android.com/media/camera/camera2/multi-camera)
-     *
-     * If we want to open concurrent logical cameras, which are one front camera and one back
-     * camera, the device needs to support [PackageManager.FEATURE_CAMERA_CONCURRENT]. To set up
-     * concurrent logical camera, call [availableConcurrentCameraInfos] to get the list of available
-     * combinations of concurrent cameras. Each sub-list contains the [CameraInfo]s for a
-     * combination of cameras that can be operated concurrently. Each logical camera can have its
-     * own [UseCase]s and [LifecycleOwner]. See
-     * [CameraX lifecycles]({@docRoot}training/camerax/architecture#lifecycles)
-     *
-     * If the concurrent logical cameras are binding the same preview and video capture use cases,
-     * the concurrent cameras video recording will be supported. The concurrent camera preview
-     * stream will be shared with video capture and record the concurrent cameras as a whole. The
-     * [CompositionSettings] can be used to configure the position of each camera stream.
-     *
-     * If we want to open concurrent physical cameras, which are two front cameras or two back
-     * cameras, the device needs to support physical cameras and the capability could be checked via
-     * [CameraInfo.isLogicalMultiCameraSupported]. Each physical cameras can have its own [UseCase]s
-     * but needs to have the same [LifecycleOwner], otherwise [IllegalArgumentException] will be
-     * thrown.
-     *
-     * If we want to open one physical camera, for example ultra wide, we just need to set physical
-     * camera id in [CameraSelector] and bind to lifecycle. All CameraX features will work normally
-     * when only a single physical camera is used.
-     *
-     * If we want to open multiple physical cameras, we need to have multiple [CameraSelector]s,
-     * each in one [SingleCameraConfig] and set physical camera id, then bind to lifecycle with the
-     * [SingleCameraConfig]s. Internally each physical camera id will be set on [UseCase], for
-     * example, [Preview] and call
-     * [android.hardware.camera2.params.OutputConfiguration.setPhysicalCameraId].
-     *
-     * Currently only two physical cameras for the same logical camera id are allowed and the device
-     * needs to support physical cameras by checking [CameraInfo.isLogicalMultiCameraSupported]. In
-     * addition, there is no guarantee or API to query whether the device supports multiple physical
-     * camera opening or not. Internally the library checks
-     * [android.hardware.camera2.CameraDevice.isSessionConfigurationSupported], if the device does
-     * not support the multiple physical camera configuration, [IllegalArgumentException] will be
-     * thrown.
-     *
-     * @param singleCameraConfigs Input list of [SingleCameraConfig]s.
-     * @return Output [ConcurrentCamera] instance.
-     * @throws IllegalArgumentException If less or more than two camera configs are provided.
-     * @throws UnsupportedOperationException If device is not supporting concurrent camera or
-     *   cameras are already used by other [UseCase]s.
-     * @see ConcurrentCamera
-     * @see availableConcurrentCameraInfos
-     * @see CameraInfo.isLogicalMultiCameraSupported
-     * @see CameraInfo.getPhysicalCameraInfos
-     */
-    @OptIn(ExperimentalCameraInfo::class)
     @MainThread
-    public fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera =
+    public override fun bindToLifecycle(
+        singleCameraConfigs: List<SingleCameraConfig?>
+    ): ConcurrentCamera =
         trace("CX:bindToLifecycle-Concurrent") {
             if (singleCameraConfigs.size < 2) {
                 throw IllegalArgumentException("Concurrent camera needs two camera configs.")
@@ -543,7 +428,6 @@
      *   camera to be used for the given use cases.
      */
     @Suppress("unused")
-    @OptIn(ExperimentalCameraInfo::class)
     internal fun bindToLifecycle(
         lifecycleOwner: LifecycleOwner,
         primaryCameraSelector: CameraSelector,
@@ -635,7 +519,7 @@
             return@trace lifecycleCameraToBind
         }
 
-    override fun isBound(useCase: UseCase): Boolean {
+    public override fun isBound(useCase: UseCase): Boolean {
         for (lifecycleCamera: LifecycleCamera in mLifecycleCameraRepository.lifecycleCameras) {
             if (lifecycleCamera.isBound(useCase)) {
                 return true
@@ -694,44 +578,18 @@
             return@trace true
         }
 
-    /**
-     * Returns [CameraInfo] instances of the available cameras.
-     *
-     * The available cameras include all the available cameras on the device, or only those selected
-     * through [androidx.camera.core.CameraXConfig.Builder.setAvailableCamerasLimiter].
-     *
-     * While iterating through all the available [CameraInfo], if one of them meets some predefined
-     * requirements, a [CameraSelector] that uniquely identifies its camera can be retrieved using
-     * [CameraInfo.getCameraSelector], which can then be used to bind [use cases][UseCase] to that
-     * camera.
-     *
-     * @return A list of [CameraInfo] instances for the available cameras.
-     */
-    override fun getAvailableCameraInfos(): List<CameraInfo> =
-        trace("CX:getAvailableCameraInfos") {
-            val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
-            val cameras: Set<CameraInternal> = mCameraX!!.cameraRepository.cameras
-            for (camera: CameraInternal in cameras) {
-                availableCameraInfos.add(camera.cameraInfo)
+    override val availableCameraInfos: List<CameraInfo>
+        get() =
+            trace("CX:getAvailableCameraInfos") {
+                val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
+                val cameras: Set<CameraInternal> = mCameraX!!.cameraRepository.cameras
+                for (camera: CameraInternal in cameras) {
+                    availableCameraInfos.add(camera.cameraInfo)
+                }
+                return@trace availableCameraInfos
             }
-            return@trace availableCameraInfos
-        }
 
-    public val availableConcurrentCameraInfos: List<List<CameraInfo>>
-        /**
-         * Returns list of [CameraInfo] instances of the available concurrent cameras.
-         *
-         * The available concurrent cameras include all combinations of cameras which could operate
-         * concurrently on the device. Each list maps to one combination of these camera's
-         * [CameraInfo].
-         *
-         * For example, to select a front camera and a back camera and bind to [LifecycleOwner] with
-         * preview [UseCase], this function could be used with [bindToLifecycle].
-         *
-         * @sample androidx.camera.lifecycle.samples.bindConcurrentCameraSample
-         * @return List of combinations of [CameraInfo].
-         */
-        @OptIn(ExperimentalCameraInfo::class)
+    final override val availableConcurrentCameraInfos: List<List<CameraInfo>>
         get() =
             trace("CX:getAvailableConcurrentCameraInfos") {
                 requireNonNull(mCameraX)
@@ -756,7 +614,6 @@
                 return@trace availableConcurrentCameraInfos
             }
 
-    @ExperimentalCameraInfo
     override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo =
         trace("CX:getCameraInfo") {
             val cameraInfoInternal =
@@ -780,12 +637,7 @@
             return@trace restrictedCameraInfo!!
         }
 
-    public val isConcurrentCameraModeOn: Boolean
-        /**
-         * Returns whether there is a [ConcurrentCamera] bound.
-         *
-         * @return `true` if there is a [ConcurrentCamera] bound, otherwise `false`.
-         */
+    final override val isConcurrentCameraModeOn: Boolean
         @MainThread get() = cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
 
     private fun getOrCreateCameraXInstance(context: Context): ListenableFuture<CameraX> {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/Recording.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/Recording.kt
index a1d2fa5..cbe00b35 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/Recording.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/Recording.kt
@@ -55,6 +55,7 @@
     private val recorder: Recorder,
     private val outputOptions: OutputOptions,
     private val withAudio: Boolean,
+    private val initialAudioMuted: Boolean,
     private val asPersistentRecording: Boolean,
     private val recordingStopStrategy: (androidx.camera.video.Recording, Recorder) -> Unit,
     private val callbackExecutor: Executor,
@@ -73,7 +74,7 @@
             else -> throw AssertionError()
         }.apply {
             if (withAudio) {
-                withAudioEnabled()
+                withAudioEnabled(initialAudioMuted)
             }
             if (asPersistentRecording) {
                 asPersistentRecording()
@@ -221,7 +222,7 @@
         return this
     }
 
-    private fun verifyMute(muted: Boolean) {
+    public fun verifyMute(muted: Boolean) {
         // TODO(b/274862085): Change to verify the status events consecutively having MUTED state
         //  by adding the utility to MockConsumer.
         try {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/RecordingSession.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/RecordingSession.kt
index f8866da..014de9f 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/RecordingSession.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/video/RecordingSession.kt
@@ -50,6 +50,7 @@
         recorder: Recorder = defaults.recorder,
         outputOptions: OutputOptions = defaults.outputOptionsProvider.invoke(),
         withAudio: Boolean = defaults.withAudio,
+        initialAudioMuted: Boolean = false,
         asPersistentRecording: Boolean = false,
     ): Recording {
         return Recording(
@@ -57,6 +58,7 @@
                 recorder = recorder,
                 outputOptions = outputOptions,
                 withAudio = withAudio,
+                initialAudioMuted = initialAudioMuted,
                 asPersistentRecording = asPersistentRecording,
                 recordingStopStrategy = defaults.recordingStopStrategy,
                 callbackExecutor = defaults.callbackExecutor,
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index beba6c4..8721077 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -84,8 +84,9 @@
 
   public final class PendingRecording {
     method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
-    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor listenerExecutor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent> listener);
     method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled(optional boolean initialMuted);
   }
 
   public class Quality {
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index beba6c4..8721077 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -84,8 +84,9 @@
 
   public final class PendingRecording {
     method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
-    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor listenerExecutor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent> listener);
     method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled(optional boolean initialMuted);
   }
 
   public class Quality {
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 d2b1913..7afd920 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
@@ -771,6 +771,19 @@
     }
 
     @Test
+    fun mute_withInitialMuted() {
+        // Arrange.
+        val recording = recordingSession.createRecording(initialAudioMuted = true)
+
+        // Act.
+        recording.startAndVerify()
+
+        // Assert.
+        recording.verifyMute(true)
+        recording.stopAndVerify()
+    }
+
+    @Test
     fun mute_noOpIfAudioDisabled() {
         // Arrange.
         val recording = recordingSession.createRecording(withAudio = false)
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
deleted file mode 100644
index bbcc840..0000000
--- a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.video;
-
-import android.Manifest;
-import android.content.Context;
-
-import androidx.annotation.CheckResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresPermission;
-import androidx.camera.core.impl.utils.ContextUtil;
-import androidx.core.content.PermissionChecker;
-import androidx.core.util.Consumer;
-import androidx.core.util.Preconditions;
-
-import java.util.concurrent.Executor;
-
-/**
- * A recording that can be started at a future time.
- *
- * <p>A pending recording allows for configuration of a recording before it is started. Once a
- * pending recording is started with {@link #start(Executor, Consumer)}, any changes to the pending
- * recording will not affect the actual recording; any modifications to the recording will need
- * to occur through the controls of the {@link Recording} class returned by
- * {@link #start(Executor, Consumer)}.
- *
- * <p>A pending recording can be created using one of the {@link Recorder} methods for starting a
- * recording such as {@link Recorder#prepareRecording(Context, MediaStoreOutputOptions)}.
-
- * <p>There may be more settings that can only be changed per-recorder instead of per-recording,
- * because it requires expensive operations like reconfiguring the camera. For those settings, use
- * the {@link Recorder.Builder} methods to configure before creating the {@link Recorder}
- * instance, then create the pending recording with it.
- */
-public final class PendingRecording {
-
-    private final Context mContext;
-    private final Recorder mRecorder;
-    private final OutputOptions mOutputOptions;
-    private Consumer<VideoRecordEvent> mEventListener;
-    private Executor mListenerExecutor;
-    private boolean mAudioEnabled = false;
-    private boolean mIsPersistent = false;
-
-    PendingRecording(@NonNull Context context, @NonNull Recorder recorder,
-            @NonNull OutputOptions options) {
-        // Application context is sufficient for all our needs, so store that to avoid leaking
-        // unused resources. For attribution, ContextUtil.getApplicationContext() will retain the
-        // attribution tag from the original context.
-        mContext = ContextUtil.getApplicationContext(context);
-        mRecorder = recorder;
-        mOutputOptions = options;
-    }
-
-    /**
-     * Returns an application context which was retrieved from the {@link Context} used to
-     * create this object.
-     */
-    @NonNull
-    Context getApplicationContext() {
-        return mContext;
-    }
-
-    @NonNull
-    Recorder getRecorder() {
-        return mRecorder;
-    }
-
-    @NonNull
-    OutputOptions getOutputOptions() {
-        return mOutputOptions;
-    }
-
-    @Nullable
-    Executor getListenerExecutor() {
-        return mListenerExecutor;
-    }
-
-    @Nullable
-    Consumer<VideoRecordEvent> getEventListener() {
-        return mEventListener;
-    }
-
-    boolean isAudioEnabled() {
-        return mAudioEnabled;
-    }
-
-    boolean isPersistent() {
-        return mIsPersistent;
-    }
-
-    /**
-     * Enables audio to be recorded for this recording.
-     *
-     * <p>This method must be called prior to {@link #start(Executor, Consumer)} to enable audio
-     * in the recording. If this method is not called, the {@link Recording} generated by
-     * {@link #start(Executor, Consumer)} will not contain audio, and
-     * {@link AudioStats#getAudioState()} will always return
-     * {@link AudioStats#AUDIO_STATE_DISABLED} for all {@link RecordingStats} send to the listener
-     * set passed to {@link #start(Executor, Consumer)}.
-     *
-     * <p>Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
-     * permission; without it, recording will fail at {@link #start(Executor, Consumer)} with an
-     * {@link IllegalStateException}.
-     *
-     * @return this pending recording
-     * @throws IllegalStateException if the {@link Recorder} this recording is associated to
-     * doesn't support audio.
-     * @throws SecurityException if the {@link Manifest.permission#RECORD_AUDIO} permission
-     * is denied for the current application.
-     */
-    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
-    @NonNull
-    public PendingRecording withAudioEnabled() {
-        // Check permissions and throw a security exception if RECORD_AUDIO is not granted.
-        if (PermissionChecker.checkSelfPermission(mContext, Manifest.permission.RECORD_AUDIO)
-                == PermissionChecker.PERMISSION_DENIED) {
-            throw new SecurityException("Attempted to enable audio for recording but application "
-                    + "does not have RECORD_AUDIO permission granted.");
-        }
-        Preconditions.checkState(mRecorder.isAudioSupported(), "The Recorder this recording is "
-                + "associated to doesn't support audio.");
-        mAudioEnabled = true;
-        return this;
-    }
-
-    /**
-     * Configures the recording to be a persistent recording.
-     *
-     * <p>A persistent recording will only be stopped by explicitly calling
-     * {@link Recording#stop()} or {@link Recording#close()} and will ignore events that would
-     * normally cause recording to stop, such as lifecycle events or explicit unbinding of a
-     * {@link VideoCapture} use case that the recording's {@link Recorder} is attached to.
-     *
-     * <p>Even though lifecycle events or explicit unbinding use cases won't stop a persistent
-     * recording, it will still stop the camera from producing data, resulting in the in-progress
-     * persistent recording stopping getting data until the camera stream is activated again. For
-     * example, when the activity goes into background, the recording will keep waiting for new
-     * data to be recorded until the activity is back to foreground.
-     *
-     * <p>A {@link Recorder} instance is recommended to be associated with a single
-     * {@link VideoCapture} instance, especially when using persistent recording. Otherwise, there
-     * might be unexpected behavior. Any in-progress persistent recording created from the same
-     * {@link Recorder} should be stopped before starting a new recording, even if the
-     * {@link Recorder} is associated with a different {@link VideoCapture}.
-     *
-     * <p>To switch to a different camera stream while a recording is in progress, first create
-     * the recording as persistent recording, then rebind the {@link VideoCapture} it's
-     * associated with to a different camera. The implementation may be like:
-     * <pre>{@code
-     * // Prepare the Recorder and VideoCapture, then bind the VideoCapture to the back camera.
-     * Recorder recorder = Recorder.Builder().build();
-     * VideoCapture videoCapture = VideoCapture.withOutput(recorder);
-     * cameraProvider.bindToLifecycle(
-     *         lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, videoCapture);
-     *
-     * // Prepare the persistent recording and start it.
-     * Recording recording = recorder
-     *         .prepareRecording(context, outputOptions)
-     *         .asPersistentRecording()
-     *         .start(eventExecutor, eventListener);
-     *
-     * // Record from the back camera for a period of time.
-     *
-     * // Rebind the VideoCapture to the front camera.
-     * cameraProvider.unbindAll();
-     * cameraProvider.bindToLifecycle(
-     *         lifecycleOwner, CameraSelector.DEFAULT_FRONT_CAMERA, videoCapture);
-     *
-     * // Record from the front camera for a period of time.
-     *
-     * // Stop the recording explicitly.
-     * recording.stop();
-     * }</pre>
-     *
-     * <p>The audio data will still be recorded after the {@link VideoCapture} is unbound.
-     * {@link Recording#pause() Pause} the recording first and {@link Recording#resume() resume} it
-     * later to stop recording audio while rebinding use cases.
-     *
-     * <p>If the recording is unable to receive data from the new camera, possibly because of
-     * incompatible surface combination, an exception will be thrown when binding to lifecycle.
-     */
-    @ExperimentalPersistentRecording
-    @NonNull
-    public PendingRecording asPersistentRecording() {
-        mIsPersistent = true;
-        return this;
-    }
-
-    /**
-     * Starts the recording, making it an active recording.
-     *
-     * <p>Only a single recording can be active at a time, so if another recording is active,
-     * this will throw an {@link IllegalStateException}.
-     *
-     * <p>If there are no errors starting the recording, the returned {@link Recording}
-     * can be used to {@link Recording#pause() pause}, {@link Recording#resume() resume},
-     * or {@link Recording#stop() stop} the recording.
-     *
-     * <p>Upon successfully starting the recording, a {@link VideoRecordEvent.Start} event will
-     * be the first event sent to the provided event listener.
-     *
-     * <p>If errors occur while starting the recording, a {@link VideoRecordEvent.Finalize} event
-     * will be the first event sent to the provided listener, and information about the error can
-     * be found in that event's {@link VideoRecordEvent.Finalize#getError()} method. The returned
-     * {@link Recording} will be in a finalized state, and all controls will be no-ops.
-     *
-     * <p>If the returned {@link Recording} is garbage collected, the recording will be
-     * automatically stopped. A reference to the active recording must be maintained as long as
-     * the recording needs to be active. If the recording is garbage collected, the
-     * {@link VideoRecordEvent.Finalize} event will contain error
-     * {@link VideoRecordEvent.Finalize#ERROR_RECORDING_GARBAGE_COLLECTED}.
-     *
-     * <p>The {@link Recording} will be stopped automatically if the {@link VideoCapture} its
-     * {@link Recorder} is attached to is unbound unless it's created
-     * {@link #asPersistentRecording() as a persistent recording}.
-     *
-     * @throws IllegalStateException if the associated Recorder currently has an unfinished
-     * active recording.
-     * @param listenerExecutor the executor that the event listener will be run on.
-     * @param listener the event listener to handle video record events.
-     */
-    @NonNull
-    @CheckResult
-    public Recording start(
-            @NonNull Executor listenerExecutor,
-            @NonNull Consumer<VideoRecordEvent> listener) {
-        Preconditions.checkNotNull(listenerExecutor, "Listener Executor can't be null.");
-        Preconditions.checkNotNull(listener, "Event listener can't be null");
-        mListenerExecutor = listenerExecutor;
-        mEventListener = listener;
-        return mRecorder.start(this);
-    }
-}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.kt b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.kt
new file mode 100644
index 0000000..c05952a
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.video
+
+import android.Manifest
+import android.content.Context
+import androidx.annotation.CheckResult
+import androidx.annotation.RequiresPermission
+import androidx.camera.core.impl.utils.ContextUtil
+import androidx.core.content.PermissionChecker
+import androidx.core.util.Consumer
+import androidx.core.util.Preconditions
+import java.util.concurrent.Executor
+
+/**
+ * A recording that can be started at a future time.
+ *
+ * A pending recording allows for configuration of a recording before it is started. Once a pending
+ * recording is started with [start], any changes to the pending recording will not affect the
+ * actual recording; any modifications to the recording will need to occur through the controls of
+ * the [Recording] class returned by [start].
+ *
+ * A pending recording can be created using one of the [Recorder] methods for starting a recording
+ * such as [Recorder.prepareRecording].
+ *
+ * There may be more settings that can only be changed per-recorder instead of per-recording,
+ * because it requires expensive operations like reconfiguring the camera. For those settings, use
+ * the [Recorder.Builder] methods to configure before creating the [Recorder] instance, then create
+ * the pending recording with it.
+ */
+public class PendingRecording
+internal constructor(
+    context: Context,
+    private val recorder: Recorder,
+    private val outputOptions: OutputOptions
+) {
+    // Application context is sufficient for all our needs, so store that to avoid leaking
+    // unused resources. For attribution, ContextUtil.getApplicationContext() will retain the
+    // attribution tag from the original context.
+    private val applicationContext: Context = ContextUtil.getApplicationContext(context)
+    private var eventListener: Consumer<VideoRecordEvent>? = null
+    private var listenerExecutor: Executor? = null
+    private var isAudioEnabled: Boolean = false
+    private var isAudioInitialMuted: Boolean = false
+    private var isPersistent: Boolean = false
+
+    /**
+     * Returns an application context which was retrieved from the [Context] used to create this
+     * object.
+     */
+    @JvmName("getApplicationContext")
+    internal fun getApplicationContext(): Context = applicationContext
+
+    @JvmName("getRecorder") internal fun getRecorder(): Recorder = recorder
+
+    @JvmName("getOutputOptions") internal fun getOutputOptions(): OutputOptions = outputOptions
+
+    @JvmName("getListenerExecutor") internal fun getListenerExecutor(): Executor? = listenerExecutor
+
+    @JvmName("getEventListener")
+    internal fun getEventListener(): Consumer<VideoRecordEvent>? = eventListener
+
+    @JvmName("isAudioEnabled") internal fun isAudioEnabled(): Boolean = isAudioEnabled
+
+    @JvmName("isAudioInitialMuted")
+    internal fun isAudioInitialMuted(): Boolean = isAudioInitialMuted
+
+    @JvmName("isPersistent") internal fun isPersistent(): Boolean = isPersistent
+
+    /**
+     * Enables audio to be recorded for this recording.
+     *
+     * This method must be called prior to [start] to enable audio in the recording. If this method
+     * is not called, the [Recording] generated by [start] will not contain audio, and
+     * [AudioStats.getAudioState] will always return [AudioStats.AUDIO_STATE_DISABLED] for all
+     * [RecordingStats] send to the listener set passed to [start].
+     *
+     * Recording with audio requires the [android.Manifest.permission.RECORD_AUDIO] permission;
+     * without it, recording will fail at [start] with an [IllegalStateException].
+     *
+     * @param initialMuted (Optional) The initial mute state of the recording. Defaults to `false`
+     *   (un-muted). After the recording is started, the mute state can be changed by calling
+     *   [Recording.mute].
+     * @return this pending recording
+     * @throws IllegalStateException if the [Recorder] this recording is associated to doesn't
+     *   support audio.
+     * @throws SecurityException if the [Manifest.permission.RECORD_AUDIO] permission is denied for
+     *   the current application.
+     */
+    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+    @JvmOverloads
+    public fun withAudioEnabled(initialMuted: Boolean = false): PendingRecording {
+        // Check permissions and throw a security exception if RECORD_AUDIO is not granted.
+        if (
+            PermissionChecker.checkSelfPermission(
+                applicationContext,
+                Manifest.permission.RECORD_AUDIO
+            ) == PermissionChecker.PERMISSION_DENIED
+        ) {
+            throw SecurityException(
+                "Attempted to enable audio for recording but application " +
+                    "does not have RECORD_AUDIO permission granted."
+            )
+        }
+        Preconditions.checkState(
+            recorder.isAudioSupported,
+            "The Recorder this recording is " + "associated to doesn't support audio."
+        )
+        isAudioEnabled = true
+        isAudioInitialMuted = initialMuted
+        return this
+    }
+
+    /**
+     * Configures the recording to be a persistent recording.
+     *
+     * A persistent recording will only be stopped by explicitly calling [Recording.stop] or
+     * [Recording.close] and will ignore events that would normally cause recording to stop, such as
+     * lifecycle events or explicit unbinding of a [VideoCapture] use case that the recording's
+     * [Recorder] is attached to.
+     *
+     * Even though lifecycle events or explicit unbinding use cases won't stop a persistent
+     * recording, it will still stop the camera from producing data, resulting in the in-progress
+     * persistent recording stopping getting data until the camera stream is activated again. For
+     * example, when the activity goes into background, the recording will keep waiting for new data
+     * to be recorded until the activity is back to foreground.
+     *
+     * A [Recorder] instance is recommended to be associated with a single [VideoCapture] instance,
+     * especially when using persistent recording. Otherwise, there might be unexpected behavior.
+     * Any in-progress persistent recording created from the same [Recorder] should be stopped
+     * before starting a new recording, even if the [Recorder] is associated with a different
+     * [VideoCapture].
+     *
+     * To switch to a different camera stream while a recording is in progress, first create the
+     * recording as persistent recording, then rebind the [VideoCapture] it's associated with to a
+     * different camera. The implementation may be like:
+     * ```
+     * // Prepare the Recorder and VideoCapture, then bind the VideoCapture to the back camera.
+     * Recorder recorder = Recorder.Builder().build();
+     * VideoCapture videoCapture = VideoCapture.withOutput(recorder);
+     * cameraProvider.bindToLifecycle(
+     *         lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, videoCapture);
+     *
+     * // Prepare the persistent recording and start it.
+     * Recording recording = recorder
+     *         .prepareRecording(context, outputOptions)
+     *         .asPersistentRecording()
+     *         .start(eventExecutor, eventListener);
+     *
+     * // Record from the back camera for a period of time.
+     *
+     * // Rebind the VideoCapture to the front camera.
+     * cameraProvider.unbindAll();
+     * cameraProvider.bindToLifecycle(
+     *         lifecycleOwner, CameraSelector.DEFAULT_FRONT_CAMERA, videoCapture);
+     *
+     *
+     * // Record from the front camera for a period of time.
+     *
+     * // Stop the recording explicitly.
+     * recording.stop();
+     * ```
+     *
+     * The audio data will still be recorded after the [VideoCapture] is unbound.
+     * [Pause][Recording.pause] the recording first and [resume][Recording.resume] it later to stop
+     * recording audio while rebinding use cases.
+     *
+     * If the recording is unable to receive data from the new camera, possibly because of
+     * incompatible surface combination, an exception will be thrown when binding to lifecycle.
+     */
+    @ExperimentalPersistentRecording
+    public fun asPersistentRecording(): PendingRecording {
+        isPersistent = true
+        return this
+    }
+
+    /**
+     * Starts the recording, making it an active recording.
+     *
+     * Only a single recording can be active at a time, so if another recording is active, this will
+     * throw an [IllegalStateException].
+     *
+     * If there are no errors starting the recording, the returned [Recording] can be used to
+     * [pause][Recording.pause], [resume][Recording.resume], or [stop][Recording.stop] the
+     * recording.
+     *
+     * Upon successfully starting the recording, a [VideoRecordEvent.Start] event will be the first
+     * event sent to the provided event listener.
+     *
+     * If errors occur while starting the recording, a [VideoRecordEvent.Finalize] event will be the
+     * first event sent to the provided listener, and information about the error can be found in
+     * that event's [VideoRecordEvent.Finalize.getError] method. The returned [Recording] will be in
+     * a finalized state, and all controls will be no-ops.
+     *
+     * If the returned [Recording] is garbage collected, the recording will be automatically
+     * stopped. A reference to the active recording must be maintained as long as the recording
+     * needs to be active. If the recording is garbage collected, the [VideoRecordEvent.Finalize]
+     * event will contain error [VideoRecordEvent.Finalize.ERROR_RECORDING_GARBAGE_COLLECTED].
+     *
+     * The [Recording] will be stopped automatically if the [VideoCapture] its [Recorder] is
+     * attached to is unbound unless it's created
+     * [as a persistent recording][asPersistentRecording].
+     *
+     * @param listenerExecutor the executor that the event listener will be run on.
+     * @param listener the event listener to handle video record events.
+     * @throws IllegalStateException if the associated Recorder currently has an unfinished active
+     *   recording.
+     */
+    @CheckResult
+    public fun start(listenerExecutor: Executor, listener: Consumer<VideoRecordEvent>): Recording {
+        Preconditions.checkNotNull(listenerExecutor, "Listener Executor can't be null.")
+        Preconditions.checkNotNull(listener, "Event listener can't be null")
+        this.listenerExecutor = listenerExecutor
+        eventListener = listener
+        return recorder.start(this)
+    }
+}
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 1250761..a79d7c5 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
@@ -2968,7 +2968,7 @@
 
         @NonNull
         static RecordingRecord from(@NonNull PendingRecording pendingRecording, long recordingId) {
-            return new AutoValue_Recorder_RecordingRecord(
+            RecordingRecord recordingRecord = new AutoValue_Recorder_RecordingRecord(
                     pendingRecording.getOutputOptions(),
                     pendingRecording.getListenerExecutor(),
                     pendingRecording.getEventListener(),
@@ -2976,6 +2976,8 @@
                     pendingRecording.isPersistent(),
                     recordingId
             );
+            recordingRecord.mute(pendingRecording.isAudioInitialMuted());
+            return recordingRecord;
         }
 
         @NonNull
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
index 953c2ef..3484029 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
@@ -178,7 +178,8 @@
      *
      * <p>The output file will contain an audio track even the whole recording is muted. Create a
      * recording without calling {@link PendingRecording#withAudioEnabled()} to record a file
-     * with no audio track.
+     * with no audio track. To set the initial mute state of the recording, use
+     * {@link PendingRecording#withAudioEnabled(boolean)}.
      *
      * <p>Muting or unmuting a recording that isn't created
      * {@link PendingRecording#withAudioEnabled()} with audio enabled is no-op.
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
index 51884e6..28c30aa 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
@@ -17,13 +17,11 @@
 package androidx.camera.view;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ExperimentalCameraInfo;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.lifecycle.ProcessCameraProvider;
@@ -73,7 +71,6 @@
         return mProcessCameraProvider.shutdownAsync();
     }
 
-    @OptIn(markerClass = ExperimentalCameraInfo.class)
     @NonNull
     @Override
     public CameraInfo getCameraInfo(CameraSelector cameraSelector) {
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index e630a5e..c8427ff 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -362,6 +362,7 @@
     private RecordUi mRecordUi;
     private DynamicRangeUi mDynamicRangeUi;
     private Quality mVideoQuality;
+    private boolean mAudioMuted = false;
     private DynamicRange mDynamicRange = DynamicRange.SDR;
     private @ImageCapture.OutputFormat int mImageOutputFormat = OUTPUT_FORMAT_JPEG;
     private Set<DynamicRange> mDisplaySupportedHighDynamicRanges = Collections.emptySet();
@@ -694,7 +695,7 @@
                         pendingRecording.asPersistentRecording();
                     }
                     mActiveRecording = pendingRecording
-                            .withAudioEnabled()
+                            .withAudioEnabled(mAudioMuted)
                             .start(ContextCompat.getMainExecutor(CameraXActivity.this),
                                     mVideoRecordEventListener);
                     mRecordUi.setState(RecordUi.State.RECORDING);
@@ -779,6 +780,17 @@
 
             popup.show();
         });
+
+        Runnable buttonMuteUpdater = () -> mRecordUi.getButtonMute().setImageResource(
+                mAudioMuted ? R.drawable.ic_mic_off : R.drawable.ic_mic_on);
+        buttonMuteUpdater.run();
+        mRecordUi.getButtonMute().setOnClickListener(view -> {
+            mAudioMuted = !mAudioMuted;
+            if (mActiveRecording != null) {
+                mActiveRecording.mute(mAudioMuted);
+            }
+            buttonMuteUpdater.run();
+        });
     }
 
     private void setUpDynamicRangeButton() {
@@ -1533,6 +1545,7 @@
                 findViewById(R.id.video_stats),
                 findViewById(R.id.video_quality),
                 findViewById(R.id.video_persistent),
+                findViewById(R.id.video_mute),
                 (newState) -> updateDynamicRangeUiState()
         );
 
@@ -2302,19 +2315,21 @@
         private final TextView mTextStats;
         private final Button mButtonQuality;
         private final ToggleButton mButtonPersistent;
+        private final ImageButton mButtonMute;
         private boolean mEnabled = false;
         private State mState = State.IDLE;
         private final Consumer<State> mNewStateConsumer;
 
         RecordUi(@NonNull Button buttonRecord, @NonNull Button buttonPause,
                 @NonNull TextView textStats, @NonNull Button buttonQuality,
-                @NonNull ToggleButton buttonPersistent,
+                @NonNull ToggleButton buttonPersistent, @NonNull ImageButton buttonMute,
                 @NonNull Consumer<State> onNewState) {
             mButtonRecord = buttonRecord;
             mButtonPause = buttonPause;
             mTextStats = textStats;
             mButtonQuality = buttonQuality;
             mButtonPersistent = buttonPersistent;
+            mButtonMute = buttonMute;
             mNewStateConsumer = onNewState;
         }
 
@@ -2325,6 +2340,7 @@
                 mTextStats.setVisibility(View.VISIBLE);
                 mButtonQuality.setVisibility(View.VISIBLE);
                 mButtonPersistent.setVisibility(View.VISIBLE);
+                mButtonMute.setVisibility(View.VISIBLE);
                 updateUi();
             } else {
                 mButtonRecord.setText("Record");
@@ -2333,6 +2349,7 @@
                 mButtonQuality.setVisibility(View.INVISIBLE);
                 mTextStats.setVisibility(View.GONE);
                 mButtonPersistent.setVisibility(View.INVISIBLE);
+                mButtonMute.setVisibility(View.INVISIBLE);
             }
         }
 
@@ -2354,6 +2371,7 @@
             mButtonPause.setVisibility(View.GONE);
             mTextStats.setVisibility(View.GONE);
             mButtonPersistent.setVisibility(View.GONE);
+            mButtonMute.setVisibility(View.GONE);
         }
 
         private void updateUi() {
@@ -2367,6 +2385,7 @@
                     mButtonPause.setText("Pause");
                     mButtonPause.setVisibility(View.INVISIBLE);
                     mButtonPersistent.setEnabled(true);
+                    mButtonMute.setEnabled(true);
                     mButtonQuality.setEnabled(true);
                     break;
                 case RECORDING:
@@ -2375,6 +2394,7 @@
                     mButtonPause.setText("Pause");
                     mButtonPause.setVisibility(View.VISIBLE);
                     mButtonPersistent.setEnabled(false);
+                    mButtonMute.setEnabled(true);
                     mButtonQuality.setEnabled(false);
                     break;
                 case STOPPING:
@@ -2383,6 +2403,7 @@
                     mButtonPause.setText("Pause");
                     mButtonPause.setVisibility(View.INVISIBLE);
                     mButtonPersistent.setEnabled(false);
+                    mButtonMute.setEnabled(false);
                     mButtonQuality.setEnabled(true);
                     break;
                 case PAUSED:
@@ -2391,6 +2412,7 @@
                     mButtonPause.setText("Resume");
                     mButtonPause.setVisibility(View.VISIBLE);
                     mButtonPersistent.setEnabled(false);
+                    mButtonMute.setEnabled(true);
                     mButtonQuality.setEnabled(true);
                     break;
             }
@@ -2416,6 +2438,10 @@
         ToggleButton getButtonPersistent() {
             return mButtonPersistent;
         }
+
+        ImageButton getButtonMute() {
+            return mButtonMute;
+        }
     }
 
     Preview getPreview() {
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index 53eb0a0..e8a8994 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -72,7 +72,6 @@
 import androidx.camera.core.ConcurrentCamera;
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
 import androidx.camera.core.DynamicRange;
-import androidx.camera.core.ExperimentalCameraInfo;
 import androidx.camera.core.ExperimentalMirrorMode;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.MeteringPoint;
@@ -839,7 +838,7 @@
     }
 
     @SuppressLint({"MissingPermission", "NullAnnotationGroup"})
-    @OptIn(markerClass = { ExperimentalCameraInfo.class, ExperimentalPersistentRecording.class})
+    @OptIn(markerClass = ExperimentalPersistentRecording.class)
     private void setUpRecordButton() {
         mRecordUi.getButtonRecord().setOnClickListener((view) -> {
             RecordUi.State state = mRecordUi.getState();
diff --git a/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_off.xml b/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_off.xml
new file mode 100644
index 0000000..26250b9
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_off.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19,11h-1.7c0,0.74 -0.16,1.43 -0.43,2.05l1.23,1.23c0.56,-0.98 0.9,-2.09 0.9,-3.28zM14.98,11.17c0,-0.06 0.02,-0.11 0.02,-0.17L15,5c0,-1.66 -1.34,-3 -3,-3s-3,1.34 -3,3v0.18l5.98,5.99zM4.27,3L3,4.27l6.01,6.01L9.01,11c0,1.66 1.33,3 2.99,3 0.22,0 0.44,-0.03 0.65,-0.08l1.66,1.66c-0.71,0.33 -1.5,0.52 -2.31,0.52 -2.76,0 -5.3,-2.1 -5.3,-5.1L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c0.91,-0.13 1.77,-0.45 2.54,-0.9L19.73,21 21,19.73L4.27,3z" />
+</vector>
diff --git a/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_on.xml b/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_on.xml
new file mode 100644
index 0000000..c0a5a06
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/res/drawable/ic_mic_on.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z" />
+</vector>
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
index 369ddd8..c65182e 100644
--- a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
@@ -145,6 +145,23 @@
         app:layout_constraintTop_toBottomOf="@id/video_quality"
         app:layout_constraintRight_toRightOf="parent" />
 
+    <ImageButton
+        android:id="@+id/video_mute"
+        android:layout_width="46dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="1dp"
+        android:layout_marginRight="5dp"
+        android:padding="5dp"
+        android:scaleType="fitXY"
+        android:adjustViewBounds="true"
+        android:visibility="invisible"
+        android:src="@drawable/ic_mic_on"
+        android:background="@drawable/round_toggle_button"
+        android:clickable="true"
+        android:focusable="true"
+        app:layout_constraintTop_toBottomOf="@id/video_persistent"
+        app:layout_constraintRight_toRightOf="parent" />
+
     <ToggleButton
         android:id="@+id/VideoToggle"
         android:layout_width="wrap_content"
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index edc8649..2466066 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -101,7 +101,6 @@
     androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":concurrent:concurrent-futures"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
-    androidTestImplementation(project(":window:window"))
 
     // Testing framework
     androidTestImplementation(libs.testExtJunit)
diff --git a/car/app/app-automotive/api/1.7.0-beta02.txt b/car/app/app-automotive/api/1.7.0-beta02.txt
new file mode 100644
index 0000000..33c4502
--- /dev/null
+++ b/car/app/app-automotive/api/1.7.0-beta02.txt
@@ -0,0 +1,101 @@
+// Signature format: 4.0
+package androidx.car.app.activity {
+
+  public abstract class BaseCarAppActivity extends androidx.fragment.app.FragmentActivity implements androidx.lifecycle.LifecycleOwner {
+    ctor public BaseCarAppActivity();
+    method public void bindToViewModel(androidx.car.app.SessionInfo);
+    method public android.content.ComponentName? getServiceComponentName();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public android.content.ComponentName? retrieveServiceComponentName();
+  }
+
+  public final class CarAppActivity extends androidx.car.app.activity.BaseCarAppActivity implements androidx.lifecycle.LifecycleOwner {
+    ctor public CarAppActivity();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class LauncherActivity extends androidx.fragment.app.FragmentActivity implements androidx.lifecycle.LifecycleOwner {
+    ctor public LauncherActivity();
+  }
+
+}
+
+package androidx.car.app.activity.renderer.surface {
+
+  @SuppressCompatibility public final class LegacySurfacePackage {
+    ctor public LegacySurfacePackage(androidx.car.app.activity.renderer.surface.SurfaceControlCallback);
+  }
+
+  public interface SurfaceControlCallback {
+    method public default void onError(String, Throwable);
+    method public void onKeyEvent(android.view.KeyEvent);
+    method public void onTouchEvent(android.view.MotionEvent);
+    method public void onWindowFocusChanged(boolean, boolean);
+    method public void setSurfaceWrapper(androidx.car.app.activity.renderer.surface.SurfaceWrapper);
+  }
+
+  @SuppressCompatibility public final class SurfaceWrapper {
+    ctor public SurfaceWrapper(android.os.IBinder?, @Dimension int, @Dimension int, int, int, android.view.Surface);
+    method public int getDensityDpi();
+    method public int getDisplayId();
+    method @Dimension public int getHeight();
+    method public android.os.IBinder? getHostToken();
+    method public android.view.Surface getSurface();
+    method @Dimension public int getWidth();
+  }
+
+}
+
+package androidx.car.app.hardware {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class AutomotiveCarHardwareManager implements androidx.car.app.hardware.CarHardwareManager {
+    ctor public AutomotiveCarHardwareManager(android.content.Context);
+  }
+
+}
+
+package androidx.car.app.hardware.common {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneAreaIdConstants {
+    field public static final int AREA_ID_GLOBAL = 0; // 0x0
+  }
+
+  public static final class CarZoneAreaIdConstants.VehicleAreaSeat {
+    field public static final int COL_ALL = 1911; // 0x777
+    field public static final int COL_CENTER = 546; // 0x222
+    field public static final int COL_LEFT = 273; // 0x111
+    field public static final int COL_RIGHT = 1092; // 0x444
+    field public static final int ROW_1_CENTER = 2; // 0x2
+    field public static final int ROW_1_LEFT = 1; // 0x1
+    field public static final int ROW_1_RIGHT = 4; // 0x4
+    field public static final int ROW_2_CENTER = 32; // 0x20
+    field public static final int ROW_2_LEFT = 16; // 0x10
+    field public static final int ROW_2_RIGHT = 64; // 0x40
+    field public static final int ROW_3_CENTER = 512; // 0x200
+    field public static final int ROW_3_LEFT = 256; // 0x100
+    field public static final int ROW_3_RIGHT = 1024; // 0x400
+    field public static final int ROW_ALL = 1911; // 0x777
+    field public static final int ROW_FIRST = 7; // 0x7
+    field public static final int ROW_SECOND = 112; // 0x70
+    field public static final int ROW_THIRD = 1792; // 0x700
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface CarZoneAreaIdConverter {
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneUtils {
+    method public static com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int, int);
+    method public static androidx.car.app.hardware.common.CarZoneAreaIdConverter getZoneAreaIdConverter(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class GlobalCarZoneAreaIdConverter implements androidx.car.app.hardware.common.CarZoneAreaIdConverter {
+    ctor public GlobalCarZoneAreaIdConverter();
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class SeatCarZoneAreaIdConverter implements androidx.car.app.hardware.common.CarZoneAreaIdConverter {
+    ctor public SeatCarZoneAreaIdConverter();
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+}
+
diff --git a/car/app/app-automotive/api/res-1.7.0-beta02.txt b/car/app/app-automotive/api/res-1.7.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car/app/app-automotive/api/res-1.7.0-beta02.txt
diff --git a/car/app/app-automotive/api/restricted_1.7.0-beta02.txt b/car/app/app-automotive/api/restricted_1.7.0-beta02.txt
new file mode 100644
index 0000000..433cb93
--- /dev/null
+++ b/car/app/app-automotive/api/restricted_1.7.0-beta02.txt
@@ -0,0 +1,101 @@
+// Signature format: 4.0
+package androidx.car.app.activity {
+
+  public abstract class BaseCarAppActivity extends androidx.fragment.app.FragmentActivity {
+    ctor public BaseCarAppActivity();
+    method public void bindToViewModel(androidx.car.app.SessionInfo);
+    method public android.content.ComponentName? getServiceComponentName();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public android.content.ComponentName? retrieveServiceComponentName();
+  }
+
+  public final class CarAppActivity extends androidx.car.app.activity.BaseCarAppActivity {
+    ctor public CarAppActivity();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class LauncherActivity extends androidx.fragment.app.FragmentActivity {
+    ctor public LauncherActivity();
+  }
+
+}
+
+package androidx.car.app.activity.renderer.surface {
+
+  @SuppressCompatibility public final class LegacySurfacePackage {
+    ctor public LegacySurfacePackage(androidx.car.app.activity.renderer.surface.SurfaceControlCallback);
+  }
+
+  public interface SurfaceControlCallback {
+    method public default void onError(String, Throwable);
+    method public void onKeyEvent(android.view.KeyEvent);
+    method public void onTouchEvent(android.view.MotionEvent);
+    method public void onWindowFocusChanged(boolean, boolean);
+    method public void setSurfaceWrapper(androidx.car.app.activity.renderer.surface.SurfaceWrapper);
+  }
+
+  @SuppressCompatibility public final class SurfaceWrapper {
+    ctor public SurfaceWrapper(android.os.IBinder?, @Dimension int, @Dimension int, int, int, android.view.Surface);
+    method public int getDensityDpi();
+    method public int getDisplayId();
+    method @Dimension public int getHeight();
+    method public android.os.IBinder? getHostToken();
+    method public android.view.Surface getSurface();
+    method @Dimension public int getWidth();
+  }
+
+}
+
+package androidx.car.app.hardware {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class AutomotiveCarHardwareManager implements androidx.car.app.hardware.CarHardwareManager {
+    ctor public AutomotiveCarHardwareManager(android.content.Context);
+  }
+
+}
+
+package androidx.car.app.hardware.common {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneAreaIdConstants {
+    field public static final int AREA_ID_GLOBAL = 0; // 0x0
+  }
+
+  public static final class CarZoneAreaIdConstants.VehicleAreaSeat {
+    field public static final int COL_ALL = 1911; // 0x777
+    field public static final int COL_CENTER = 546; // 0x222
+    field public static final int COL_LEFT = 273; // 0x111
+    field public static final int COL_RIGHT = 1092; // 0x444
+    field public static final int ROW_1_CENTER = 2; // 0x2
+    field public static final int ROW_1_LEFT = 1; // 0x1
+    field public static final int ROW_1_RIGHT = 4; // 0x4
+    field public static final int ROW_2_CENTER = 32; // 0x20
+    field public static final int ROW_2_LEFT = 16; // 0x10
+    field public static final int ROW_2_RIGHT = 64; // 0x40
+    field public static final int ROW_3_CENTER = 512; // 0x200
+    field public static final int ROW_3_LEFT = 256; // 0x100
+    field public static final int ROW_3_RIGHT = 1024; // 0x400
+    field public static final int ROW_ALL = 1911; // 0x777
+    field public static final int ROW_FIRST = 7; // 0x7
+    field public static final int ROW_SECOND = 112; // 0x70
+    field public static final int ROW_THIRD = 1792; // 0x700
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface CarZoneAreaIdConverter {
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneUtils {
+    method public static com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int, int);
+    method public static androidx.car.app.hardware.common.CarZoneAreaIdConverter getZoneAreaIdConverter(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class GlobalCarZoneAreaIdConverter implements androidx.car.app.hardware.common.CarZoneAreaIdConverter {
+    ctor public GlobalCarZoneAreaIdConverter();
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class SeatCarZoneAreaIdConverter implements androidx.car.app.hardware.common.CarZoneAreaIdConverter {
+    ctor public SeatCarZoneAreaIdConverter();
+    method public com.google.common.collect.ImmutableSet<androidx.car.app.hardware.common.CarZone!> convertAreaIdToCarZones(int);
+  }
+
+}
+
diff --git a/car/app/app-projected/api/1.7.0-beta02.txt b/car/app/app-projected/api/1.7.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/car/app/app-projected/api/1.7.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/car/app/app-projected/api/res-1.7.0-beta02.txt b/car/app/app-projected/api/res-1.7.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car/app/app-projected/api/res-1.7.0-beta02.txt
diff --git a/car/app/app-projected/api/restricted_1.7.0-beta02.txt b/car/app/app-projected/api/restricted_1.7.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/car/app/app-projected/api/restricted_1.7.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
index 10c4c7e..ac0ca6d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
@@ -110,11 +110,11 @@
     <string name="route_list_limit" msgid="505793441615134116">"Абмежаванне спіса маршрутаў"</string>
     <string name="content_limits" msgid="5726880972110281095">"Абмежаванні змесціва"</string>
     <string name="content_limits_demo_title" msgid="3207211638386727610">"Дэманстрацыя абмежаванняў змесціва"</string>
-    <string name="finish_app_msg" msgid="8354334557053141891">"Гэта дзеянне закрые праграму, а калі вы зноў запусціце яе, з\'явіцца папярэдне зададзены экран дазволаў"</string>
+    <string name="finish_app_msg" msgid="8354334557053141891">"Гэта дзеянне закрые праграму, а калі вы зноў запусціце яе, з’явіцца папярэдне зададзены экран дазволаў"</string>
     <string name="finish_app_title" msgid="9013328479438745074">"Завяршыць дэманстрацыю праграмы"</string>
-    <string name="finish_app_demo_title" msgid="8223819062053448384">"Дэманстрацыя папярэдне зададзенага экрана дазволаў, які з\'явіцца пры наступным запуску праграмы"</string>
+    <string name="finish_app_demo_title" msgid="8223819062053448384">"Дэманстрацыя папярэдне зададзенага экрана дазволаў, які з’явіцца пры наступным запуску праграмы"</string>
     <string name="preseed_permission_app_title" msgid="182847662545676962">"Дэманстрацыя праграмы з папярэдне зададзенымі дазволамі"</string>
-    <string name="preseed_permission_demo_title" msgid="5476541421753978071">"Дэманстрацыя экрана папярэдне зададзеных дазволаў, які з\'явіцца пры наступным запуску праграмы"</string>
+    <string name="preseed_permission_demo_title" msgid="5476541421753978071">"Дэманстрацыя экрана папярэдне зададзеных дазволаў, які з’явіцца пры наступным запуску праграмы"</string>
     <string name="loading_demo_title" msgid="1086529475809143517">"Загружаецца дэманстрацыя"</string>
     <string name="loading_demo_row_title" msgid="8933049915126088142">"Загрузка завершана!"</string>
     <string name="pop_to_root" msgid="2078277386355064198">"Перайсці да першага экрана"</string>
diff --git a/car/app/app-testing/api/1.7.0-beta02.txt b/car/app/app-testing/api/1.7.0-beta02.txt
new file mode 100644
index 0000000..b478e9b
--- /dev/null
+++ b/car/app/app-testing/api/1.7.0-beta02.txt
@@ -0,0 +1,72 @@
+// Signature format: 4.0
+package androidx.car.app.testing {
+
+  public class FakeHost {
+    method public void performNotificationActionClick(android.app.PendingIntent);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void setMicrophoneInputData(java.io.InputStream);
+  }
+
+  public class ScreenController {
+    ctor public ScreenController(androidx.car.app.Screen);
+    method public androidx.car.app.Screen getScreen();
+    method public Object? getScreenResult();
+    method public java.util.List<androidx.car.app.model.Template!> getTemplatesReturned();
+    method public androidx.car.app.testing.ScreenController moveToState(androidx.lifecycle.Lifecycle.State);
+    method public void reset();
+  }
+
+  public class SessionController {
+    ctor public SessionController(androidx.car.app.Session, androidx.car.app.testing.TestCarContext, android.content.Intent);
+    method public androidx.car.app.Session getSession();
+    method public androidx.car.app.testing.SessionController moveToState(androidx.lifecycle.Lifecycle.State);
+  }
+
+  public class TestAppManager extends androidx.car.app.AppManager {
+    method public androidx.car.app.SurfaceCallback? getSurfaceCallback();
+    method public java.util.List<android.util.Pair<androidx.car.app.Screen!,androidx.car.app.model.Template!>!> getTemplatesReturned();
+    method public java.util.List<java.lang.CharSequence!> getToastsShown();
+    method public void reset();
+  }
+
+  public class TestCarContext extends androidx.car.app.CarContext {
+    method public static androidx.car.app.testing.TestCarContext createCarContext(android.content.Context);
+    method public androidx.car.app.testing.FakeHost getFakeHost();
+    method public androidx.car.app.testing.TestCarContext.PermissionRequestInfo? getLastPermissionRequestInfo();
+    method public java.util.List<android.content.Intent!> getStartCarAppIntents();
+    method public boolean hasCalledFinishCarApp();
+    method public void reset();
+  }
+
+  public static class TestCarContext.PermissionRequestInfo {
+    method public androidx.car.app.OnRequestPermissionsListener getListener();
+    method public java.util.List<java.lang.String!> getPermissionsRequested();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+    method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+    method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+    field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+  }
+
+  public class TestScreenManager extends androidx.car.app.ScreenManager {
+    method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
+    method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
+    method public boolean hasScreens();
+    method public void reset();
+  }
+
+}
+
+package androidx.car.app.testing.navigation {
+
+  public class TestNavigationManager extends androidx.car.app.navigation.NavigationManager {
+    ctor public TestNavigationManager(androidx.car.app.testing.TestCarContext, androidx.car.app.HostDispatcher);
+    method public int getNavigationEndedCount();
+    method public androidx.car.app.navigation.NavigationManagerCallback? getNavigationManagerCallback();
+    method public int getNavigationStartedCount();
+    method public java.util.List<androidx.car.app.navigation.model.Trip!> getTripsSent();
+    method public void reset();
+  }
+
+}
+
diff --git a/car/app/app-testing/api/res-1.7.0-beta02.txt b/car/app/app-testing/api/res-1.7.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car/app/app-testing/api/res-1.7.0-beta02.txt
diff --git a/car/app/app-testing/api/restricted_1.7.0-beta02.txt b/car/app/app-testing/api/restricted_1.7.0-beta02.txt
new file mode 100644
index 0000000..b478e9b
--- /dev/null
+++ b/car/app/app-testing/api/restricted_1.7.0-beta02.txt
@@ -0,0 +1,72 @@
+// Signature format: 4.0
+package androidx.car.app.testing {
+
+  public class FakeHost {
+    method public void performNotificationActionClick(android.app.PendingIntent);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void setMicrophoneInputData(java.io.InputStream);
+  }
+
+  public class ScreenController {
+    ctor public ScreenController(androidx.car.app.Screen);
+    method public androidx.car.app.Screen getScreen();
+    method public Object? getScreenResult();
+    method public java.util.List<androidx.car.app.model.Template!> getTemplatesReturned();
+    method public androidx.car.app.testing.ScreenController moveToState(androidx.lifecycle.Lifecycle.State);
+    method public void reset();
+  }
+
+  public class SessionController {
+    ctor public SessionController(androidx.car.app.Session, androidx.car.app.testing.TestCarContext, android.content.Intent);
+    method public androidx.car.app.Session getSession();
+    method public androidx.car.app.testing.SessionController moveToState(androidx.lifecycle.Lifecycle.State);
+  }
+
+  public class TestAppManager extends androidx.car.app.AppManager {
+    method public androidx.car.app.SurfaceCallback? getSurfaceCallback();
+    method public java.util.List<android.util.Pair<androidx.car.app.Screen!,androidx.car.app.model.Template!>!> getTemplatesReturned();
+    method public java.util.List<java.lang.CharSequence!> getToastsShown();
+    method public void reset();
+  }
+
+  public class TestCarContext extends androidx.car.app.CarContext {
+    method public static androidx.car.app.testing.TestCarContext createCarContext(android.content.Context);
+    method public androidx.car.app.testing.FakeHost getFakeHost();
+    method public androidx.car.app.testing.TestCarContext.PermissionRequestInfo? getLastPermissionRequestInfo();
+    method public java.util.List<android.content.Intent!> getStartCarAppIntents();
+    method public boolean hasCalledFinishCarApp();
+    method public void reset();
+  }
+
+  public static class TestCarContext.PermissionRequestInfo {
+    method public androidx.car.app.OnRequestPermissionsListener getListener();
+    method public java.util.List<java.lang.String!> getPermissionsRequested();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+    method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+    method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+    field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+  }
+
+  public class TestScreenManager extends androidx.car.app.ScreenManager {
+    method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
+    method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
+    method public boolean hasScreens();
+    method public void reset();
+  }
+
+}
+
+package androidx.car.app.testing.navigation {
+
+  public class TestNavigationManager extends androidx.car.app.navigation.NavigationManager {
+    ctor public TestNavigationManager(androidx.car.app.testing.TestCarContext, androidx.car.app.HostDispatcher);
+    method public int getNavigationEndedCount();
+    method public androidx.car.app.navigation.NavigationManagerCallback? getNavigationManagerCallback();
+    method public int getNavigationStartedCount();
+    method public java.util.List<androidx.car.app.navigation.model.Trip!> getTripsSent();
+    method public void reset();
+  }
+
+}
+
diff --git a/car/app/app/api/1.7.0-beta02.txt b/car/app/app/api/1.7.0-beta02.txt
new file mode 100644
index 0000000..63d6309
--- /dev/null
+++ b/car/app/app/api/1.7.0-beta02.txt
@@ -0,0 +1,2456 @@
+// Signature format: 4.0
+package androidx.car.app {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class AppInfo {
+    ctor @VisibleForTesting public AppInfo(int, int, String);
+    method public int getLatestCarAppApiLevel();
+    method public String getLibraryDisplayVersion();
+    method public int getMinCarAppApiLevel();
+    field public static final String MIN_API_LEVEL_METADATA_KEY = "androidx.car.app.minCarApiLevel";
+  }
+
+  public class AppManager implements androidx.car.app.managers.Manager {
+    method @androidx.car.app.annotations.RequiresCarApi(5) public void dismissAlert(int);
+    method public void invalidate();
+    method public void setSurfaceCallback(androidx.car.app.SurfaceCallback?);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public void showAlert(androidx.car.app.model.Alert);
+    method public void showToast(CharSequence, int);
+  }
+
+  public final class CarAppPermission {
+    method public static void checkHasLibraryPermission(android.content.Context, String);
+    method public static void checkHasPermission(android.content.Context, String);
+    field public static final String ACCESS_SURFACE = "androidx.car.app.ACCESS_SURFACE";
+    field public static final String MAP_TEMPLATES = "androidx.car.app.MAP_TEMPLATES";
+    field public static final String NAVIGATION_TEMPLATES = "androidx.car.app.NAVIGATION_TEMPLATES";
+  }
+
+  public abstract class CarAppService extends android.app.Service {
+    ctor public CarAppService();
+    method public abstract androidx.car.app.validation.HostValidator createHostValidator();
+    method @CallSuper public final void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
+    method @Deprecated public final androidx.car.app.Session? getCurrentSession();
+    method public final androidx.car.app.HostInfo? getHostInfo();
+    method public final androidx.car.app.Session? getSession(androidx.car.app.SessionInfo);
+    method @CallSuper public final android.os.IBinder onBind(android.content.Intent);
+    method public androidx.car.app.Session onCreateSession();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.Session onCreateSession(androidx.car.app.SessionInfo);
+    method public final boolean onUnbind(android.content.Intent);
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_CALLING_APP = "androidx.car.app.category.CALLING";
+    field @Deprecated public static final String CATEGORY_CHARGING_APP = "androidx.car.app.category.CHARGING";
+    field @androidx.car.app.annotations.RequiresCarApi(6) public static final String CATEGORY_FEATURE_CLUSTER = "androidx.car.app.category.FEATURE_CLUSTER";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_IOT_APP = "androidx.car.app.category.IOT";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_MESSAGING_APP = "androidx.car.app.category.MESSAGING";
+    field public static final String CATEGORY_NAVIGATION_APP = "androidx.car.app.category.NAVIGATION";
+    field @Deprecated public static final String CATEGORY_PARKING_APP = "androidx.car.app.category.PARKING";
+    field public static final String CATEGORY_POI_APP = "androidx.car.app.category.POI";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_SETTINGS_APP = "androidx.car.app.category.SETTINGS";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_WEATHER_APP = "androidx.car.app.category.WEATHER";
+    field public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
+  }
+
+  public class CarContext extends android.content.ContextWrapper {
+    method public void finishCarApp();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public android.content.ComponentName? getCallingComponent();
+    method public int getCarAppApiLevel();
+    method public <T> T getCarService(Class<T!>);
+    method public Object getCarService(String);
+    method public String getCarServiceName(Class<? extends java.lang.Object!>);
+    method public androidx.car.app.HostInfo? getHostInfo();
+    method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+    method public boolean isDarkMode();
+    method public void requestPermissions(java.util.List<java.lang.String!>, androidx.car.app.OnRequestPermissionsListener);
+    method public void requestPermissions(java.util.List<java.lang.String!>, java.util.concurrent.Executor, androidx.car.app.OnRequestPermissionsListener);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public void setCarAppResult(int, android.content.Intent?);
+    method public void startCarApp(android.content.Intent);
+    method @Deprecated public static void startCarApp(android.content.Intent, android.content.Intent);
+    field public static final String ACTION_NAVIGATE = "androidx.car.app.action.NAVIGATE";
+    field public static final String APP_SERVICE = "app";
+    field public static final String CAR_SERVICE = "car";
+    field @androidx.car.app.annotations.RequiresCarApi(2) public static final String CONSTRAINT_SERVICE = "constraints";
+    field public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra.START_CAR_APP_BINDER_KEY";
+    field @androidx.car.app.annotations.RequiresCarApi(3) public static final String HARDWARE_SERVICE = "hardware";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String MEDIA_PLAYBACK_SERVICE = "media_playback";
+    field public static final String NAVIGATION_SERVICE = "navigation";
+    field public static final String SCREEN_SERVICE = "screen";
+    field public static final String SUGGESTION_SERVICE = "suggestion";
+  }
+
+  public final class CarToast {
+    method public static androidx.car.app.CarToast makeText(androidx.car.app.CarContext, @StringRes int, int);
+    method public static androidx.car.app.CarToast makeText(androidx.car.app.CarContext, CharSequence, int);
+    method public void setDuration(int);
+    method public void setText(@StringRes int);
+    method public void setText(CharSequence);
+    method public void show();
+    field public static final int LENGTH_LONG = 1; // 0x1
+    field public static final int LENGTH_SHORT = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class FailureResponse {
+    ctor public FailureResponse(Throwable);
+    method public int getErrorType();
+    method public String getStackTrace();
+    field public static final int BUNDLER_EXCEPTION = 1; // 0x1
+    field public static final int ILLEGAL_STATE_EXCEPTION = 2; // 0x2
+    field public static final int INVALID_PARAMETER_EXCEPTION = 3; // 0x3
+    field public static final int REMOTE_EXCEPTION = 6; // 0x6
+    field public static final int RUNTIME_EXCEPTION = 5; // 0x5
+    field public static final int SECURITY_EXCEPTION = 4; // 0x4
+    field public static final int UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class HandshakeInfo {
+    ctor public HandshakeInfo(String, int);
+    method public int getHostCarAppApiLevel();
+    method public String getHostPackageName();
+  }
+
+  public final class HostException extends java.lang.RuntimeException {
+    ctor public HostException(String);
+    ctor public HostException(String, Throwable);
+    ctor public HostException(Throwable);
+  }
+
+  public final class HostInfo {
+    ctor public HostInfo(String, int);
+    method public String getPackageName();
+    method public int getUid();
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnDoneCallback {
+    method public default void onFailure(androidx.car.app.serialization.Bundleable);
+    method public default void onSuccess(androidx.car.app.serialization.Bundleable?);
+  }
+
+  public interface OnRequestPermissionsListener {
+    method public void onRequestPermissionsResult(java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
+  }
+
+  public interface OnScreenResultListener {
+    method public void onScreenResult(Object?);
+  }
+
+  public abstract class Screen implements androidx.lifecycle.LifecycleOwner {
+    ctor protected Screen(androidx.car.app.CarContext);
+    method public final void finish();
+    method public final androidx.car.app.CarContext getCarContext();
+    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method public String? getMarker();
+    method public final androidx.car.app.ScreenManager getScreenManager();
+    method public final void invalidate();
+    method public abstract androidx.car.app.model.Template onGetTemplate();
+    method public void setMarker(String?);
+    method public void setResult(Object?);
+  }
+
+  @MainThread public class ScreenManager implements androidx.car.app.managers.Manager {
+    method public java.util.Collection<androidx.car.app.Screen!> getScreenStack();
+    method public int getStackSize();
+    method public androidx.car.app.Screen getTop();
+    method public void pop();
+    method public void popTo(String);
+    method public void popToRoot();
+    method public void push(androidx.car.app.Screen);
+    method public void pushForResult(androidx.car.app.Screen, androidx.car.app.OnScreenResultListener);
+    method public void remove(androidx.car.app.Screen);
+  }
+
+  public abstract class Session implements androidx.lifecycle.LifecycleOwner {
+    ctor public Session();
+    method public final androidx.car.app.CarContext getCarContext();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public void onCarConfigurationChanged(android.content.res.Configuration);
+    method public abstract androidx.car.app.Screen onCreateScreen(android.content.Intent);
+    method public void onNewIntent(android.content.Intent);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class SessionInfo {
+    ctor public SessionInfo(int, String);
+    method public int getDisplayType();
+    method public String getSessionId();
+    method public java.util.Set<java.lang.Class<? extends androidx.car.app.model.Template!>!>? getSupportedTemplates(int);
+    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
+  }
+
+  public class SessionInfoIntentEncoder {
+    method public static boolean containsSessionInfo(android.content.Intent);
+    method public static androidx.car.app.SessionInfo decode(android.content.Intent);
+    method public static void encode(androidx.car.app.SessionInfo, android.content.Intent);
+  }
+
+  public interface SurfaceCallback {
+    method @androidx.car.app.annotations.RequiresCarApi(5) public default void onClick(float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onFling(float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onScale(float, float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onScroll(float, float);
+    method public default void onStableAreaChanged(android.graphics.Rect);
+    method public default void onSurfaceAvailable(androidx.car.app.SurfaceContainer);
+    method public default void onSurfaceDestroyed(androidx.car.app.SurfaceContainer);
+    method public default void onVisibleAreaChanged(android.graphics.Rect);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SurfaceContainer {
+    ctor public SurfaceContainer(android.view.Surface?, int, int, int);
+    method public int getDpi();
+    method public int getHeight();
+    method public android.view.Surface? getSurface();
+    method public int getWidth();
+  }
+
+}
+
+package androidx.car.app.annotations {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface CarProtocol {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalCarApi {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface RequiresCarApi {
+    method public abstract int value();
+  }
+
+}
+
+package androidx.car.app.connection {
+
+  public final class CarConnection {
+    ctor @MainThread public CarConnection(android.content.Context);
+    method public androidx.lifecycle.LiveData<java.lang.Integer!> getType();
+    field public static final String ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED";
+    field public static final String CAR_CONNECTION_STATE = "CarConnectionState";
+    field public static final int CONNECTION_TYPE_NATIVE = 1; // 0x1
+    field public static final int CONNECTION_TYPE_NOT_CONNECTED = 0; // 0x0
+    field public static final int CONNECTION_TYPE_PROJECTION = 2; // 0x2
+  }
+
+}
+
+package androidx.car.app.constraints {
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public class ConstraintManager implements androidx.car.app.managers.Manager {
+    method public int getContentLimit(int);
+    method @androidx.car.app.annotations.RequiresCarApi(6) public boolean isAppDrivenRefreshEnabled();
+    field public static final int CONTENT_LIMIT_TYPE_GRID = 1; // 0x1
+    field public static final int CONTENT_LIMIT_TYPE_LIST = 0; // 0x0
+    field public static final int CONTENT_LIMIT_TYPE_PANE = 4; // 0x4
+    field public static final int CONTENT_LIMIT_TYPE_PLACE_LIST = 2; // 0x2
+    field public static final int CONTENT_LIMIT_TYPE_ROUTE_LIST = 3; // 0x3
+  }
+
+}
+
+package androidx.car.app.features {
+
+  public final class CarFeatures {
+    method public static boolean isFeatureEnabled(android.content.Context, String);
+    field public static final String FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING = "background_audio_while_driving";
+  }
+
+}
+
+package androidx.car.app.hardware {
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarHardwareManager extends androidx.car.app.managers.Manager {
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public default androidx.car.app.hardware.climate.CarClimate getCarClimate();
+    method public default androidx.car.app.hardware.info.CarInfo getCarInfo();
+    method public default androidx.car.app.hardware.info.CarSensors getCarSensors();
+  }
+
+}
+
+package androidx.car.app.hardware.climate {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CabinTemperatureProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Float!,java.lang.Float!>!> getCarZoneSetsToCabinCelsiusTemperatureRanges();
+    method public float getCelsiusSupportedIncrement();
+    method public float getFahrenheitSupportedIncrement();
+    method public android.util.Pair<java.lang.Float!,java.lang.Float!> getSupportedMinMaxCelsiusRange();
+    method public android.util.Pair<java.lang.Float!,java.lang.Float!> getSupportedMinMaxFahrenheitRange();
+    method public boolean hasCarZoneSetsToCabinCelsiusTemperatureRanges();
+    method public boolean hasCelsiusSupportedIncrement();
+    method public boolean hasFahrenheitSupportedIncrement();
+    method public boolean hasSupportedMinMaxCelsiusRange();
+    method public boolean hasSupportedMinMaxFahrenheitRange();
+  }
+
+  public static final class CabinTemperatureProfile.Builder {
+    ctor public CabinTemperatureProfile.Builder();
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile build();
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setCarZoneSetsToCabinCelsiusTemperatureRanges(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Float!,java.lang.Float!>!>);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setCelsiusSupportedIncrement(float);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setFahrenheitSupportedIncrement(float);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setSupportedMinMaxCelsiusRange(android.util.Pair<java.lang.Float!,java.lang.Float!>);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setSupportedMinMaxFahrenheitRange(android.util.Pair<java.lang.Float!,java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @MainThread @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimate {
+    method public void fetchClimateProfile(java.util.concurrent.Executor, androidx.car.app.hardware.climate.ClimateProfileRequest, androidx.car.app.hardware.climate.CarClimateProfileCallback);
+    method public void registerClimateStateCallback(java.util.concurrent.Executor, androidx.car.app.hardware.climate.RegisterClimateStateRequest, androidx.car.app.hardware.climate.CarClimateStateCallback);
+    method public <E> void setClimateState(java.util.concurrent.Executor, androidx.car.app.hardware.climate.ClimateStateRequest<E!>, androidx.car.app.hardware.common.CarSetOperationStatusCallback);
+    method public void unregisterClimateStateCallback(androidx.car.app.hardware.climate.CarClimateStateCallback);
+  }
+
+  @SuppressCompatibility @MainThread @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class CarClimateFeature {
+    method public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getFeature();
+  }
+
+  public static final class CarClimateFeature.Builder {
+    ctor public CarClimateFeature.Builder(int);
+    method public androidx.car.app.hardware.climate.CarClimateFeature.Builder addCarZones(androidx.car.app.hardware.common.CarZone!...);
+    method public androidx.car.app.hardware.climate.CarClimateFeature build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimateProfileCallback {
+    method public default void onCabinTemperatureProfileAvailable(androidx.car.app.hardware.climate.CabinTemperatureProfile);
+    method public default void onCarZoneMappingInfoProfileAvailable(androidx.car.app.hardware.climate.CarZoneMappingInfoProfile);
+    method public default void onDefrosterProfileAvailable(androidx.car.app.hardware.climate.DefrosterProfile);
+    method public default void onElectricDefrosterProfileAvailable(androidx.car.app.hardware.climate.ElectricDefrosterProfile);
+    method public default void onFanDirectionProfileAvailable(androidx.car.app.hardware.climate.FanDirectionProfile);
+    method public default void onFanSpeedLevelProfileAvailable(androidx.car.app.hardware.climate.FanSpeedLevelProfile);
+    method public default void onHvacAcProfileAvailable(androidx.car.app.hardware.climate.HvacAcProfile);
+    method public default void onHvacAutoModeProfileAvailable(androidx.car.app.hardware.climate.HvacAutoModeProfile);
+    method public default void onHvacAutoRecirculationProfileAvailable(androidx.car.app.hardware.climate.HvacAutoRecirculationProfile);
+    method public default void onHvacDualModeProfileAvailable(androidx.car.app.hardware.climate.HvacDualModeProfile);
+    method public default void onHvacMaxAcModeProfileAvailable(androidx.car.app.hardware.climate.HvacMaxAcModeProfile);
+    method public default void onHvacPowerProfileAvailable(androidx.car.app.hardware.climate.HvacPowerProfile);
+    method public default void onHvacRecirculationProfileAvailable(androidx.car.app.hardware.climate.HvacRecirculationProfile);
+    method public default void onMaxDefrosterProfileAvailable(androidx.car.app.hardware.climate.MaxDefrosterProfile);
+    method public default void onSeatTemperatureLevelProfileAvailable(androidx.car.app.hardware.climate.SeatTemperatureProfile);
+    method public default void onSeatVentilationLevelProfileAvailable(androidx.car.app.hardware.climate.SeatVentilationProfile);
+    method public default void onSteeringWheelHeatProfileAvailable(androidx.car.app.hardware.climate.SteeringWheelHeatProfile);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimateStateCallback {
+    method public default void onCabinTemperatureStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public default void onDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onElectricDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onFanDirectionStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onFanSpeedLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onHvacAcStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacAutoModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacAutoRecirculationStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacDualModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacMaxAcModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacPowerStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacRecirculationStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onMaxDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onSeatTemperatureLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onSeatVentilationLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onSteeringWheelHeatStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneMappingInfoProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class CarZoneMappingInfoProfile.Builder {
+    ctor public CarZoneMappingInfoProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.CarZoneMappingInfoProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class ClimateProfileRequest {
+    method public java.util.Set<java.lang.Integer!> getAllClimateProfiles();
+    method public java.util.List<androidx.car.app.hardware.climate.CarClimateFeature!> getClimateProfileFeatures();
+    field public static final int FEATURE_CABIN_TEMPERATURE = 4; // 0x4
+    field public static final int FEATURE_CAR_ZONE_MAPPING = 17; // 0x11
+    field public static final int FEATURE_FAN_DIRECTION = 6; // 0x6
+    field public static final int FEATURE_FAN_SPEED = 5; // 0x5
+    field public static final int FEATURE_HVAC_AC = 2; // 0x2
+    field public static final int FEATURE_HVAC_AUTO_MODE = 12; // 0xc
+    field public static final int FEATURE_HVAC_AUTO_RECIRCULATION = 11; // 0xb
+    field public static final int FEATURE_HVAC_DEFROSTER = 14; // 0xe
+    field public static final int FEATURE_HVAC_DUAL_MODE = 13; // 0xd
+    field public static final int FEATURE_HVAC_ELECTRIC_DEFROSTER = 16; // 0x10
+    field public static final int FEATURE_HVAC_MAX_AC = 3; // 0x3
+    field public static final int FEATURE_HVAC_MAX_DEFROSTER = 15; // 0xf
+    field public static final int FEATURE_HVAC_POWER = 1; // 0x1
+    field public static final int FEATURE_HVAC_RECIRCULATION = 10; // 0xa
+    field public static final int FEATURE_SEAT_TEMPERATURE_LEVEL = 7; // 0x7
+    field public static final int FEATURE_SEAT_VENTILATION_LEVEL = 8; // 0x8
+    field public static final int FEATURE_STEERING_WHEEL_HEAT = 9; // 0x9
+  }
+
+  public static final class ClimateProfileRequest.Builder {
+    ctor public ClimateProfileRequest.Builder();
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest.Builder addClimateProfileFeatures(androidx.car.app.hardware.climate.CarClimateFeature!...);
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest build();
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest.Builder setAllClimateProfiles();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class ClimateStateRequest<T> {
+    method public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getRequestedFeature();
+    method public T getRequestedValue();
+  }
+
+  public static final class ClimateStateRequest.Builder<T> {
+    ctor public ClimateStateRequest.Builder(int, T!);
+    method public androidx.car.app.hardware.climate.ClimateStateRequest.Builder<T!> addCarZones(androidx.car.app.hardware.common.CarZone);
+    method public androidx.car.app.hardware.climate.ClimateStateRequest<T!> build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class DefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class DefrosterProfile.Builder {
+    ctor public DefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.DefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class ElectricDefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class ElectricDefrosterProfile.Builder {
+    ctor public ElectricDefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.ElectricDefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class FanDirectionProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,java.util.Set<java.lang.Integer!>!> getCarZoneSetsToFanDirectionValues();
+  }
+
+  public static final class FanDirectionProfile.Builder {
+    ctor public FanDirectionProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,java.util.Set<java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.FanDirectionProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class FanSpeedLevelProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToFanSpeedLevelRanges();
+  }
+
+  public static final class FanSpeedLevelProfile.Builder {
+    ctor public FanSpeedLevelProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.FanSpeedLevelProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAcProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAcProfile.Builder {
+    ctor public HvacAcProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAcProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAutoModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAutoModeProfile.Builder {
+    ctor public HvacAutoModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAutoModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAutoRecirculationProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAutoRecirculationProfile.Builder {
+    ctor public HvacAutoRecirculationProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAutoRecirculationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacDualModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacDualModeProfile.Builder {
+    ctor public HvacDualModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacDualModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacMaxAcModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacMaxAcModeProfile.Builder {
+    ctor public HvacMaxAcModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacMaxAcModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacPowerProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacPowerProfile.Builder {
+    ctor public HvacPowerProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacPowerProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacRecirculationProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZones();
+  }
+
+  public static final class HvacRecirculationProfile.Builder {
+    ctor public HvacRecirculationProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacRecirculationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class MaxDefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class MaxDefrosterProfile.Builder {
+    ctor public MaxDefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.MaxDefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class RegisterClimateStateRequest {
+    method public java.util.List<androidx.car.app.hardware.climate.CarClimateFeature!> getClimateRegisterFeatures();
+  }
+
+  public static final class RegisterClimateStateRequest.Builder {
+    ctor public RegisterClimateStateRequest.Builder(boolean);
+    method public androidx.car.app.hardware.climate.RegisterClimateStateRequest.Builder addClimateRegisterFeatures(androidx.car.app.hardware.climate.CarClimateFeature!...);
+    method public androidx.car.app.hardware.climate.RegisterClimateStateRequest build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SeatTemperatureProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSeatTemperatureValues();
+  }
+
+  public static final class SeatTemperatureProfile.Builder {
+    ctor public SeatTemperatureProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SeatTemperatureProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SeatVentilationProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSeatVentilationValues();
+  }
+
+  public static final class SeatVentilationProfile.Builder {
+    ctor public SeatVentilationProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SeatVentilationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SteeringWheelHeatProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSteeringWheelHeatValues();
+  }
+
+  public static final class SteeringWheelHeatProfile.Builder {
+    ctor public SteeringWheelHeatProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SteeringWheelHeatProfile build();
+  }
+
+}
+
+package androidx.car.app.hardware.common {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarSetOperationStatusCallback {
+    method public default void onSetCarClimateStateCabinTemperature(int);
+    method public default void onSetCarClimateStateDefroster(int);
+    method public default void onSetCarClimateStateElectricDefroster(int);
+    method public default void onSetCarClimateStateFanDirection(int);
+    method public default void onSetCarClimateStateFanSpeedLevel(int);
+    method public default void onSetCarClimateStateHvacAc(int);
+    method public default void onSetCarClimateStateHvacAutoMode(int);
+    method public default void onSetCarClimateStateHvacAutoRecirculation(int);
+    method public default void onSetCarClimateStateHvacDualMode(int);
+    method public default void onSetCarClimateStateHvacMaxAcMode(int);
+    method public default void onSetCarClimateStateHvacPower(int);
+    method public default void onSetCarClimateStateHvacRecirculation(int);
+    method public default void onSetCarClimateStateMaxDefroster(int);
+    method public default void onSetCarClimateStateSeatTemperatureLevel(int);
+    method public default void onSetCarClimateStateSeatVentilationLevel(int);
+    method public default void onSetCarClimateStateSteeringWheelHeat(int);
+    method public static String toString(int);
+    field public static final int OPERATION_STATUS_FEATURE_SETTING_NOT_ALLOWED = 4; // 0x4
+    field public static final int OPERATION_STATUS_FEATURE_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FEATURE_UNIMPLEMENTED = 1; // 0x1
+    field public static final int OPERATION_STATUS_FEATURE_UNSUPPORTED = 2; // 0x2
+    field public static final int OPERATION_STATUS_ILLEGAL_CAR_HARDWARE_STATE = 7; // 0x7
+    field public static final int OPERATION_STATUS_INSUFFICIENT_PERMISSION = 6; // 0x6
+    field public static final int OPERATION_STATUS_SUCCESS = 0; // 0x0
+    field public static final int OPERATION_STATUS_UNSUPPORTED_VALUE = 5; // 0x5
+    field public static final int OPERATION_STATUS_UPDATE_TIMEOUT = 8; // 0x8
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarUnit {
+    method public static String toString(int);
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int IMPERIAL_GALLON = 204; // 0xcc
+    field public static final int KILOMETER = 3; // 0x3
+    field public static final int KILOMETERS_PER_HOUR = 102; // 0x66
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int LITER = 202; // 0xca
+    field public static final int METER = 2; // 0x2
+    field public static final int METERS_PER_SEC = 101; // 0x65
+    field public static final int MILE = 4; // 0x4
+    field public static final int MILES_PER_HOUR = 103; // 0x67
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int MILLILITER = 201; // 0xc9
+    field public static final int MILLIMETER = 1; // 0x1
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int US_GALLON = 203; // 0xcb
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarValue<T> {
+    ctor public CarValue(T?, long, int);
+    ctor @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public CarValue(T?, long, int, java.util.List<androidx.car.app.hardware.common.CarZone!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getStatus();
+    method public long getTimestampMillis();
+    method public T? getValue();
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_UNAVAILABLE = 3; // 0x3
+    field public static final int STATUS_UNIMPLEMENTED = 2; // 0x2
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class CarZone {
+    method public int getColumn();
+    method public int getRow();
+    field public static final int CAR_ZONE_COLUMN_ALL = 16; // 0x10
+    field public static final int CAR_ZONE_COLUMN_CENTER = 48; // 0x30
+    field public static final int CAR_ZONE_COLUMN_DRIVER = 80; // 0x50
+    field public static final int CAR_ZONE_COLUMN_LEFT = 32; // 0x20
+    field public static final int CAR_ZONE_COLUMN_PASSENGER = 96; // 0x60
+    field public static final int CAR_ZONE_COLUMN_RIGHT = 64; // 0x40
+    field public static final androidx.car.app.hardware.common.CarZone CAR_ZONE_GLOBAL;
+    field public static final int CAR_ZONE_ROW_ALL = 0; // 0x0
+    field public static final int CAR_ZONE_ROW_EXCLUDE_FIRST = 4; // 0x4
+    field public static final int CAR_ZONE_ROW_FIRST = 1; // 0x1
+    field public static final int CAR_ZONE_ROW_SECOND = 2; // 0x2
+    field public static final int CAR_ZONE_ROW_THIRD = 3; // 0x3
+  }
+
+  public static final class CarZone.Builder {
+    ctor public CarZone.Builder();
+    method public androidx.car.app.hardware.common.CarZone build();
+    method public androidx.car.app.hardware.common.CarZone.Builder setColumn(int);
+    method public androidx.car.app.hardware.common.CarZone.Builder setRow(int);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public interface OnCarDataAvailableListener<T> {
+    method public void onCarDataAvailable(T);
+  }
+
+}
+
+package androidx.car.app.hardware.info {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Accelerometer {
+    ctor public Accelerometer(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getForces();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarHardwareLocation {
+    ctor public CarHardwareLocation(androidx.car.app.hardware.common.CarValue<android.location.Location!>);
+    method public androidx.car.app.hardware.common.CarValue<android.location.Location!> getLocation();
+  }
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarInfo {
+    method public void addEnergyLevelListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyLevel!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void addEvStatusListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EvStatus!>);
+    method public void addMileageListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Mileage!>);
+    method public void addSpeedListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Speed!>);
+    method public void addTollListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.TollCard!>);
+    method public void fetchEnergyProfile(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyProfile!>);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public default void fetchExteriorDimensions(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.ExteriorDimensions!>);
+    method public void fetchModel(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Model!>);
+    method public void removeEnergyLevelListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyLevel!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void removeEvStatusListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EvStatus!>);
+    method public void removeMileageListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Mileage!>);
+    method public void removeSpeedListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Speed!>);
+    method public void removeTollListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.TollCard!>);
+  }
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarSensors {
+    method public void addAccelerometerListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Accelerometer!>);
+    method public void addCarHardwareLocationListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.CarHardwareLocation!>);
+    method public void addCompassListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Compass!>);
+    method public void addGyroscopeListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Gyroscope!>);
+    method public void removeAccelerometerListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Accelerometer!>);
+    method public void removeCarHardwareLocationListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.CarHardwareLocation!>);
+    method public void removeCompassListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Compass!>);
+    method public void removeGyroscopeListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Gyroscope!>);
+    field public static final int UPDATE_RATE_FASTEST = 3; // 0x3
+    field public static final int UPDATE_RATE_NORMAL = 1; // 0x1
+    field public static final int UPDATE_RATE_UI = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Compass {
+    ctor public Compass(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getOrientations();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class EnergyLevel {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getBatteryPercent();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEnergyIsLow();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getFuelPercent();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getFuelVolumeDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemainingMeters();
+  }
+
+  public static final class EnergyLevel.Builder {
+    ctor public EnergyLevel.Builder();
+    method public androidx.car.app.hardware.info.EnergyLevel build();
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setBatteryPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setEnergyIsLow(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelVolumeDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemainingMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class EnergyProfile {
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!> getEvConnectorTypes();
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!> getFuelTypes();
+    field public static final int EVCONNECTOR_TYPE_CHADEMO = 3; // 0x3
+    field public static final int EVCONNECTOR_TYPE_COMBO_1 = 4; // 0x4
+    field public static final int EVCONNECTOR_TYPE_COMBO_2 = 5; // 0x5
+    field public static final int EVCONNECTOR_TYPE_GBT = 9; // 0x9
+    field public static final int EVCONNECTOR_TYPE_GBT_DC = 10; // 0xa
+    field public static final int EVCONNECTOR_TYPE_J1772 = 1; // 0x1
+    field public static final int EVCONNECTOR_TYPE_MENNEKES = 2; // 0x2
+    field public static final int EVCONNECTOR_TYPE_OTHER = 101; // 0x65
+    field public static final int EVCONNECTOR_TYPE_SCAME = 11; // 0xb
+    field public static final int EVCONNECTOR_TYPE_TESLA_HPWC = 7; // 0x7
+    field public static final int EVCONNECTOR_TYPE_TESLA_ROADSTER = 6; // 0x6
+    field public static final int EVCONNECTOR_TYPE_TESLA_SUPERCHARGER = 8; // 0x8
+    field public static final int EVCONNECTOR_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FUEL_TYPE_BIODIESEL = 5; // 0x5
+    field public static final int FUEL_TYPE_CNG = 8; // 0x8
+    field public static final int FUEL_TYPE_DIESEL_1 = 3; // 0x3
+    field public static final int FUEL_TYPE_DIESEL_2 = 4; // 0x4
+    field public static final int FUEL_TYPE_E85 = 6; // 0x6
+    field public static final int FUEL_TYPE_ELECTRIC = 10; // 0xa
+    field public static final int FUEL_TYPE_HYDROGEN = 11; // 0xb
+    field public static final int FUEL_TYPE_LEADED = 2; // 0x2
+    field public static final int FUEL_TYPE_LNG = 9; // 0x9
+    field public static final int FUEL_TYPE_LPG = 7; // 0x7
+    field public static final int FUEL_TYPE_OTHER = 12; // 0xc
+    field public static final int FUEL_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FUEL_TYPE_UNLEADED = 1; // 0x1
+  }
+
+  public static final class EnergyProfile.Builder {
+    ctor public EnergyProfile.Builder();
+    method public androidx.car.app.hardware.info.EnergyProfile build();
+    method public androidx.car.app.hardware.info.EnergyProfile.Builder setEvConnectorTypes(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.info.EnergyProfile.Builder setFuelTypes(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class EvStatus {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEvChargePortConnected();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEvChargePortOpen();
+  }
+
+  public static final class EvStatus.Builder {
+    ctor public EvStatus.Builder();
+    method public androidx.car.app.hardware.info.EvStatus build();
+    method public androidx.car.app.hardware.info.EvStatus.Builder setEvChargePortConnected(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public androidx.car.app.hardware.info.EvStatus.Builder setEvChargePortOpen(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class ExteriorDimensions {
+    ctor public ExteriorDimensions();
+    ctor public ExteriorDimensions(androidx.car.app.hardware.common.CarValue<java.lang.Integer![]!>);
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer![]!> getExteriorDimensions();
+    field public static final int CURB_TO_CURB_TURNING_RADIUS_INDEX = 7; // 0x7
+    field public static final int HEIGHT_INDEX = 0; // 0x0
+    field public static final int LENGTH_INDEX = 1; // 0x1
+    field public static final int TRACK_WIDTH_FRONT_INDEX = 5; // 0x5
+    field public static final int TRACK_WIDTH_REAR_INDEX = 6; // 0x6
+    field public static final int WHEEL_BASE_INDEX = 4; // 0x4
+    field public static final int WIDTH_INCLUDING_MIRRORS_INDEX = 3; // 0x3
+    field public static final int WIDTH_INDEX = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Gyroscope {
+    ctor public Gyroscope(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getRotations();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Mileage {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometerMeters();
+  }
+
+  public static final class Mileage.Builder {
+    ctor public Mileage.Builder();
+    method public androidx.car.app.hardware.info.Mileage build();
+    method public androidx.car.app.hardware.info.Mileage.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.Mileage.Builder setOdometerMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Model {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.String!> getManufacturer();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.String!> getName();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getYear();
+  }
+
+  public static final class Model.Builder {
+    ctor public Model.Builder();
+    method public androidx.car.app.hardware.info.Model build();
+    method public androidx.car.app.hardware.info.Model.Builder setManufacturer(androidx.car.app.hardware.common.CarValue<java.lang.String!>);
+    method public androidx.car.app.hardware.info.Model.Builder setName(androidx.car.app.hardware.common.CarValue<java.lang.String!>);
+    method public androidx.car.app.hardware.info.Model.Builder setYear(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Speed {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeedMetersPerSecond();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeedMetersPerSecond();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getSpeedDisplayUnit();
+  }
+
+  public static final class Speed.Builder {
+    ctor public Speed.Builder();
+    method public androidx.car.app.hardware.info.Speed build();
+    method public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.Speed.Builder setRawSpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.Speed.Builder setSpeedDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class TollCard {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getCardState();
+    field public static final int TOLLCARD_STATE_INVALID = 2; // 0x2
+    field public static final int TOLLCARD_STATE_NOT_INSERTED = 3; // 0x3
+    field public static final int TOLLCARD_STATE_UNKNOWN = 0; // 0x0
+    field public static final int TOLLCARD_STATE_VALID = 1; // 0x1
+  }
+
+  public static final class TollCard.Builder {
+    ctor public TollCard.Builder();
+    method public androidx.car.app.hardware.info.TollCard build();
+    method public androidx.car.app.hardware.info.TollCard.Builder setCardState(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+}
+
+package androidx.car.app.managers {
+
+  public interface Manager {
+  }
+
+}
+
+package androidx.car.app.media {
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public interface CarAudioCallback {
+    method public void onStopRecording();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public class CarAudioCallbackDelegate {
+    method public void onStopRecording();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public abstract class CarAudioRecord {
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.car.app.media.CarAudioRecord create(androidx.car.app.CarContext);
+    method public int read(byte[], int, int);
+    method public void startRecording();
+    method public void stopRecording();
+    field public static final int AUDIO_CONTENT_BUFFER_SIZE = 512; // 0x200
+    field public static final String AUDIO_CONTENT_MIME = "audio/l16";
+    field public static final int AUDIO_CONTENT_SAMPLING_RATE = 16000; // 0x3e80
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class MediaPlaybackManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void registerMediaPlaybackToken(android.support.v4.media.session.MediaSessionCompat.Token);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class OpenMicrophoneRequest {
+    method public androidx.car.app.media.CarAudioCallbackDelegate getCarAudioCallbackDelegate();
+  }
+
+  public static final class OpenMicrophoneRequest.Builder {
+    ctor public OpenMicrophoneRequest.Builder(androidx.car.app.media.CarAudioCallback);
+    method public androidx.car.app.media.OpenMicrophoneRequest build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class OpenMicrophoneResponse {
+    method public androidx.car.app.media.CarAudioCallbackDelegate getCarAudioCallback();
+    method public java.io.InputStream getCarMicrophoneInputStream();
+  }
+
+  public static final class OpenMicrophoneResponse.Builder {
+    ctor public OpenMicrophoneResponse.Builder(androidx.car.app.media.CarAudioCallback);
+    method public androidx.car.app.media.OpenMicrophoneResponse build();
+    method public androidx.car.app.media.OpenMicrophoneResponse.Builder setCarMicrophoneDescriptor(android.os.ParcelFileDescriptor);
+  }
+
+}
+
+package androidx.car.app.media.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(8) public class MediaPlaybackTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.Header? getHeader();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class MediaPlaybackTemplate.Builder {
+    ctor public MediaPlaybackTemplate.Builder();
+    ctor public MediaPlaybackTemplate.Builder(androidx.car.app.media.model.MediaPlaybackTemplate);
+    method public androidx.car.app.media.model.MediaPlaybackTemplate build();
+    method public androidx.car.app.media.model.MediaPlaybackTemplate.Builder setHeader(androidx.car.app.model.Header?);
+  }
+
+}
+
+package androidx.car.app.mediaextensions {
+
+  public final class MediaBrowserExtras {
+    field public static final String KEY_HINT_VIEW_MAX_CATEGORY_GRID_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_CATEGORY_GRID_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_CATEGORY_LIST_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_CATEGORY_LIST_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_GRID_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_GRID_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_ITEMS_WHILE_RESTRICTED = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_ITEMS_WHILE_RESTRICTED";
+    field public static final String KEY_HINT_VIEW_MAX_LIST_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_LIST_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_ROOT_HINT_MAX_QUEUE_ITEMS_WHILE_RESTRICTED = "androidx.car.app.mediaextensions.KEY_ROOT_HINT_MAX_QUEUE_ITEMS_WHILE_RESTRICTED";
+    field public static final String KEY_ROOT_HINT_MEDIA_SESSION_API = "androidx.car.app.mediaextensions.KEY_ROOT_HINT_MEDIA_SESSION_API";
+  }
+
+  public final class MetadataExtras {
+    field public static final String KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI = "androidx.car.app.mediaextensions.KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI";
+    field public static final String KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI = "androidx.car.app.mediaextensions.KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI";
+    field public static final String KEY_DESCRIPTION_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+    field public static final String KEY_IMMERSIVE_AUDIO = "androidx.car.app.mediaextensions.KEY_IMMERSIVE_AUDIO";
+    field public static final String KEY_SUBTITLE_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics {
+
+  public class Constants {
+    field public static final String ACTION_ANALYTICS = "androidx.car.app.mediaextensions.analytics.action.ANALYTICS";
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics.client {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface AnalyticsCallback {
+    method public void onBrowseNodeChangeEvent(androidx.car.app.mediaextensions.analytics.event.BrowseChangeEvent);
+    method public void onErrorEvent(androidx.car.app.mediaextensions.analytics.event.ErrorEvent);
+    method public void onMediaClickedEvent(androidx.car.app.mediaextensions.analytics.event.MediaClickedEvent);
+    method public default void onUnknownEvent(android.os.Bundle);
+    method public void onViewChangeEvent(androidx.car.app.mediaextensions.analytics.event.ViewChangeEvent);
+    method public void onVisibleItemsEvent(androidx.car.app.mediaextensions.analytics.event.VisibleItemsEvent);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class RootHintsPopulator {
+    ctor public RootHintsPopulator(android.os.Bundle);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setAnalyticsOptIn(boolean);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setShareOem(boolean);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setSharePlatform(boolean);
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics.event {
+
+  public abstract class AnalyticsEvent {
+    method public int getAnalyticsVersion();
+    method public String getComponent();
+    method public int getEventType();
+    method public long getTimestampMillis();
+    field public static final int EVENT_TYPE_BROWSE_NODE_CHANGED_EVENT = 3; // 0x3
+    field public static final int EVENT_TYPE_ERROR_EVENT = 5; // 0x5
+    field public static final int EVENT_TYPE_MEDIA_CLICKED_EVENT = 2; // 0x2
+    field public static final int EVENT_TYPE_UNKNOWN_EVENT = 0; // 0x0
+    field public static final int EVENT_TYPE_VIEW_CHANGE_EVENT = 4; // 0x4
+    field public static final int EVENT_TYPE_VISIBLE_ITEMS_EVENT = 1; // 0x1
+    field public static final int VIEW_ACTION_HIDE = 0; // 0x0
+    field public static final int VIEW_ACTION_MODE_NONE = 0; // 0x0
+    field public static final int VIEW_ACTION_MODE_SCROLL = 1; // 0x1
+    field public static final int VIEW_ACTION_SHOW = 1; // 0x1
+    field public static final int VIEW_COMPONENT_BROWSE_ACTION_OVERFLOW = 8; // 0x8
+    field public static final int VIEW_COMPONENT_BROWSE_LIST = 1; // 0x1
+    field public static final int VIEW_COMPONENT_BROWSE_TABS = 2; // 0x2
+    field public static final int VIEW_COMPONENT_ERROR_MESSAGE = 10; // 0xa
+    field public static final int VIEW_COMPONENT_LAUNCHER = 6; // 0x6
+    field public static final int VIEW_COMPONENT_MEDIA_HOST = 9; // 0x9
+    field public static final int VIEW_COMPONENT_MINI_PLAYBACK = 5; // 0x5
+    field public static final int VIEW_COMPONENT_PLAYBACK = 4; // 0x4
+    field public static final int VIEW_COMPONENT_QUEUE_LIST = 3; // 0x3
+    field public static final int VIEW_COMPONENT_SETTINGS_VIEW = 7; // 0x7
+    field public static final int VIEW_COMPONENT_UNKNOWN_COMPONENT = 0; // 0x0
+  }
+
+  public class BrowseChangeEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getBrowseMode();
+    method public String getBrowseNodeId();
+    method public int getViewAction();
+    field public static final int BROWSE_MODE_LINK = 4; // 0x4
+    field public static final int BROWSE_MODE_LINK_BROWSE = 5; // 0x5
+    field public static final int BROWSE_MODE_SEARCH_BROWSE = 6; // 0x6
+    field public static final int BROWSE_MODE_SEARCH_RESULTS = 7; // 0x7
+    field public static final int BROWSE_MODE_TREE_BROWSE = 2; // 0x2
+    field public static final int BROWSE_MODE_TREE_ROOT = 1; // 0x1
+    field public static final int BROWSE_MODE_TREE_TAB = 3; // 0x3
+    field public static final int BROWSE_MODE_UNKNOWN = 0; // 0x0
+  }
+
+  public class ErrorEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getErrorCode();
+    field public static final int ERROR_CODE_INVALID_BUNDLE = 1; // 0x1
+    field public static final int ERROR_CODE_INVALID_EVENT = 2; // 0x2
+    field public static final int ERROR_CODE_INVALID_EXTRAS = 0; // 0x0
+  }
+
+  public class MediaClickedEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public String? getMediaId();
+    method public int getViewComponent();
+  }
+
+  public class ViewChangeEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getViewAction();
+    method public int getViewComponent();
+  }
+
+  public class VisibleItemsEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public java.util.List<java.lang.String!>? getItemsIds();
+    method public String? getNodeId();
+    method public int getViewAction();
+    method public int getViewActionMode();
+    method public int getViewComponent();
+  }
+
+}
+
+package androidx.car.app.messaging {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class MessagingServiceConstants {
+    field public static final String ACTION_HANDLE_CAR_MESSAGING = "androidx.car.app.messaging.action.HANDLE_CAR_MESSAGING";
+  }
+
+}
+
+package androidx.car.app.messaging.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class CarMessage {
+    method public androidx.car.app.model.CarText? getBody();
+    method public String? getMultimediaMimeType();
+    method public android.net.Uri? getMultimediaUri();
+    method public long getReceivedTimeEpochMillis();
+    method public androidx.core.app.Person? getSender();
+    method public boolean isRead();
+  }
+
+  public static final class CarMessage.Builder {
+    ctor public CarMessage.Builder();
+    method public androidx.car.app.messaging.model.CarMessage build();
+    method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaMimeType(String?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaUri(android.net.Uri?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person?);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface ConversationCallback {
+    method public void onMarkAsRead();
+    method public void onTextReply(String);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public interface ConversationCallbackDelegate {
+    method public void sendMarkAsRead(androidx.car.app.OnDoneCallback);
+    method public void sendTextReply(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class ConversationItem implements androidx.car.app.model.Item {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.messaging.model.ConversationCallbackDelegate getConversationCallbackDelegate();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getId();
+    method public java.util.List<androidx.car.app.messaging.model.CarMessage!> getMessages();
+    method public androidx.core.app.Person getSelf();
+    method public androidx.car.app.model.CarText getTitle();
+    method public boolean isGroupConversation();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+  }
+
+  public static final class ConversationItem.Builder {
+    ctor public ConversationItem.Builder();
+    ctor public ConversationItem.Builder(androidx.car.app.messaging.model.ConversationItem);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.messaging.model.ConversationItem build();
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setConversationCallback(androidx.car.app.messaging.model.ConversationCallback);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setId(String);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.messaging.model.ConversationItem.Builder setIndexable(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setMessages(java.util.List<androidx.car.app.messaging.model.CarMessage!>);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setSelf(androidx.core.app.Person);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setTitle(androidx.car.app.model.CarText);
+  }
+
+}
+
+package androidx.car.app.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Action {
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public int getFlags();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public int getType();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+    method public boolean isStandard();
+    method public static String typeToString(int);
+    field public static final androidx.car.app.model.Action APP_ICON;
+    field public static final androidx.car.app.model.Action BACK;
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final androidx.car.app.model.Action COMPOSE_MESSAGE;
+    field @androidx.car.app.annotations.RequiresCarApi(5) public static final int FLAG_DEFAULT = 4; // 0x4
+    field @androidx.car.app.annotations.RequiresCarApi(5) public static final int FLAG_IS_PERSISTENT = 2; // 0x2
+    field @androidx.car.app.annotations.RequiresCarApi(4) public static final int FLAG_PRIMARY = 1; // 0x1
+    field public static final androidx.car.app.model.Action PAN;
+    field public static final int TYPE_APP_ICON = 65538; // 0x10002
+    field public static final int TYPE_BACK = 65539; // 0x10003
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int TYPE_COMPOSE_MESSAGE = 65541; // 0x10005
+    field public static final int TYPE_CUSTOM = 1; // 0x1
+    field public static final int TYPE_PAN = 65540; // 0x10004
+  }
+
+  public static final class Action.Builder {
+    ctor public Action.Builder();
+    ctor @androidx.car.app.annotations.RequiresCarApi(2) public Action.Builder(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Action build();
+    method public androidx.car.app.model.Action.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Action.Builder setEnabled(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.Action.Builder setFlags(int);
+    method public androidx.car.app.model.Action.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Action.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.Action.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Action.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ActionStrip {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Action? getFirstActionOfType(int);
+  }
+
+  public static final class ActionStrip.Builder {
+    ctor public ActionStrip.Builder();
+    method public androidx.car.app.model.ActionStrip.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ActionStrip build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Alert {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.AlertCallbackDelegate? getCallbackDelegate();
+    method public long getDurationMillis();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getId();
+    method public androidx.car.app.model.CarText? getSubtitle();
+    method public androidx.car.app.model.CarText getTitle();
+    field public static final int DURATION_SHOW_INDEFINITELY = 2147483647; // 0x7fffffff
+  }
+
+  public static final class Alert.Builder {
+    ctor public Alert.Builder(int, androidx.car.app.model.CarText, long);
+    method public androidx.car.app.model.Alert.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Alert build();
+    method public androidx.car.app.model.Alert.Builder setCallback(androidx.car.app.model.AlertCallback);
+    method public androidx.car.app.model.Alert.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Alert.Builder setSubtitle(androidx.car.app.model.CarText);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface AlertCallback {
+    method public void onCancel(int);
+    method public void onDismiss();
+    field public static final int REASON_NOT_SUPPORTED = 3; // 0x3
+    field public static final int REASON_TIMEOUT = 1; // 0x1
+    field public static final int REASON_USER_ACTION = 2; // 0x2
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface AlertCallbackDelegate {
+    method public void sendCancel(int, androidx.car.app.OnDoneCallback);
+    method public void sendDismiss(androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class Badge {
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public boolean hasDot();
+  }
+
+  public static final class Badge.Builder {
+    ctor public Badge.Builder();
+    method public androidx.car.app.model.Badge build();
+    method public androidx.car.app.model.Badge.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.Badge.Builder setHasDot(boolean);
+    method public androidx.car.app.model.Badge.Builder setIcon(androidx.car.app.model.CarIcon);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarColor {
+    method public static androidx.car.app.model.CarColor createCustom(@ColorInt int, @ColorInt int);
+    method @ColorInt public int getColor();
+    method @ColorInt public int getColorDark();
+    method public int getType();
+    field public static final androidx.car.app.model.CarColor BLUE;
+    field public static final androidx.car.app.model.CarColor DEFAULT;
+    field public static final androidx.car.app.model.CarColor GREEN;
+    field public static final androidx.car.app.model.CarColor PRIMARY;
+    field public static final androidx.car.app.model.CarColor RED;
+    field public static final androidx.car.app.model.CarColor SECONDARY;
+    field public static final int TYPE_BLUE = 6; // 0x6
+    field public static final int TYPE_CUSTOM = 0; // 0x0
+    field public static final int TYPE_DEFAULT = 1; // 0x1
+    field public static final int TYPE_GREEN = 5; // 0x5
+    field public static final int TYPE_PRIMARY = 2; // 0x2
+    field public static final int TYPE_RED = 4; // 0x4
+    field public static final int TYPE_SECONDARY = 3; // 0x3
+    field public static final int TYPE_YELLOW = 7; // 0x7
+    field public static final androidx.car.app.model.CarColor YELLOW;
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarIcon {
+    method public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method public androidx.car.app.model.CarColor? getTint();
+    method public int getType();
+    field public static final androidx.car.app.model.CarIcon ALERT;
+    field public static final androidx.car.app.model.CarIcon APP_ICON;
+    field public static final androidx.car.app.model.CarIcon BACK;
+    field @androidx.car.app.annotations.RequiresCarApi(7) public static final androidx.car.app.model.CarIcon COMPOSE_MESSAGE;
+    field public static final androidx.car.app.model.CarIcon ERROR;
+    field @androidx.car.app.annotations.RequiresCarApi(2) public static final androidx.car.app.model.CarIcon PAN;
+    field public static final int TYPE_ALERT = 4; // 0x4
+    field public static final int TYPE_APP_ICON = 5; // 0x5
+    field public static final int TYPE_BACK = 3; // 0x3
+    field public static final int TYPE_COMPOSE_MESSAGE = 8; // 0x8
+    field public static final int TYPE_CUSTOM = 1; // 0x1
+    field public static final int TYPE_ERROR = 6; // 0x6
+    field public static final int TYPE_PAN = 7; // 0x7
+  }
+
+  public static final class CarIcon.Builder {
+    ctor public CarIcon.Builder(androidx.car.app.model.CarIcon);
+    ctor public CarIcon.Builder(androidx.core.graphics.drawable.IconCompat);
+    method public androidx.car.app.model.CarIcon build();
+    method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarIconSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon);
+    method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon, int);
+    method public int getAlignment();
+    method public androidx.car.app.model.CarIcon getIcon();
+    field public static final int ALIGN_BASELINE = 1; // 0x1
+    field public static final int ALIGN_BOTTOM = 0; // 0x0
+    field public static final int ALIGN_CENTER = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarLocation {
+    method public static androidx.car.app.model.CarLocation create(android.location.Location);
+    method public static androidx.car.app.model.CarLocation create(double, double);
+    method public double getLatitude();
+    method public double getLongitude();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public class CarSpan extends android.text.style.CharacterStyle {
+    ctor public CarSpan();
+    method public void updateDrawState(android.text.TextPaint);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarText {
+    method public static androidx.car.app.model.CarText create(CharSequence);
+    method public java.util.List<java.lang.CharSequence!> getVariants();
+    method public boolean isEmpty();
+    method public static boolean isNullOrEmpty(androidx.car.app.model.CarText?);
+    method public CharSequence toCharSequence();
+  }
+
+  @SuppressCompatibility public static final class CarText.Builder {
+    ctor public CarText.Builder(CharSequence);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.CarText.Builder addVariant(CharSequence);
+    method public androidx.car.app.model.CarText build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(2) public final class ClickableSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.ClickableSpan create(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.OnClickDelegate getOnClickDelegate();
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public interface Content {
+    method public String getContentId();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DateTimeWithZone {
+    method @RequiresApi(26) public static androidx.car.app.model.DateTimeWithZone create(java.time.ZonedDateTime);
+    method public static androidx.car.app.model.DateTimeWithZone create(long, @IntRange(from=0xffff02e0, to=64800) int, String);
+    method public static androidx.car.app.model.DateTimeWithZone create(long, java.util.TimeZone);
+    method public long getTimeSinceEpochMillis();
+    method public int getZoneOffsetSeconds();
+    method public String? getZoneShortName();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Distance {
+    method public static androidx.car.app.model.Distance create(double, int);
+    method public double getDisplayDistance();
+    method public int getDisplayUnit();
+    field public static final int UNIT_FEET = 6; // 0x6
+    field public static final int UNIT_KILOMETERS = 2; // 0x2
+    field public static final int UNIT_KILOMETERS_P1 = 3; // 0x3
+    field public static final int UNIT_METERS = 1; // 0x1
+    field public static final int UNIT_MILES = 4; // 0x4
+    field public static final int UNIT_MILES_P1 = 5; // 0x5
+    field public static final int UNIT_YARDS = 7; // 0x7
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DistanceSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.DistanceSpan create(androidx.car.app.model.Distance);
+    method public androidx.car.app.model.Distance getDistance();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DurationSpan extends androidx.car.app.model.CarSpan {
+    method @RequiresApi(26) public static androidx.car.app.model.DurationSpan create(java.time.Duration);
+    method public static androidx.car.app.model.DurationSpan create(long);
+    method public long getDurationSeconds();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.ForegroundCarColorSpan create(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.CarColor getColor();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridItem implements androidx.car.app.model.Item {
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Badge? getBadge();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public int getImageType();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public androidx.car.app.model.CarText? getText();
+    method public androidx.car.app.model.CarText? getTitle();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+    method public boolean isLoading();
+    field public static final int IMAGE_TYPE_ICON = 1; // 0x1
+    field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
+  }
+
+  public static final class GridItem.Builder {
+    ctor public GridItem.Builder();
+    method public androidx.car.app.model.GridItem build();
+    method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
+    method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setIndexable(boolean);
+    method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
+    method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.GridItem.Builder setText(CharSequence);
+    method public androidx.car.app.model.GridItem.Builder setTitle(androidx.car.app.model.CarText?);
+    method public androidx.car.app.model.GridItem.Builder setTitle(CharSequence?);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class GridSection extends androidx.car.app.model.Section<androidx.car.app.model.GridItem!> {
+    method public int getItemImageShape();
+    method public int getItemSize();
+    field public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+    field public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+    field public static final int ITEM_SIZE_LARGE = 3; // 0x3
+    field public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+    field public static final int ITEM_SIZE_SMALL = 1; // 0x1
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
+    ctor public GridSection.Builder();
+    method public androidx.car.app.model.GridSection build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemImageShape();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemSize();
+    method public androidx.car.app.model.ItemList? getSingleList();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_LARGE = 4; // 0x4
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_SMALL = 1; // 0x1
+  }
+
+  public static final class GridTemplate.Builder {
+    ctor public GridTemplate.Builder();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.GridTemplate build();
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
+    method public androidx.car.app.model.GridTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.GridTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Header {
+    method public java.util.List<androidx.car.app.model.Action!> getEndHeaderActions();
+    method public androidx.car.app.model.Action? getStartHeaderAction();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class Header.Builder {
+    ctor public Header.Builder();
+    method public androidx.car.app.model.Header.Builder addEndHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Header build();
+    method public androidx.car.app.model.Header.Builder setStartHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Header.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Header.Builder setTitle(CharSequence);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public interface InputCallback {
+    method public default void onInputSubmitted(String);
+    method public default void onInputTextChanged(String);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public interface InputCallbackDelegate {
+    method public void sendInputSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void sendInputTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface Item {
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ItemList {
+    method public java.util.List<androidx.car.app.model.Item!> getItems();
+    method public androidx.car.app.model.CarText? getNoItemsMessage();
+    method public androidx.car.app.model.OnItemVisibilityChangedDelegate? getOnItemVisibilityChangedDelegate();
+    method public androidx.car.app.model.OnSelectedDelegate? getOnSelectedDelegate();
+    method public int getSelectedIndex();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ItemList.Builder toBuilder();
+  }
+
+  public static final class ItemList.Builder {
+    ctor public ItemList.Builder();
+    method public androidx.car.app.model.ItemList.Builder addItem(androidx.car.app.model.Item);
+    method public androidx.car.app.model.ItemList build();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ItemList.Builder clearItems();
+    method public androidx.car.app.model.ItemList.Builder setNoItemsMessage(CharSequence);
+    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangedListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener);
+    method public androidx.car.app.model.ItemList.Builder setOnSelectedListener(androidx.car.app.model.ItemList.OnSelectedListener);
+    method public androidx.car.app.model.ItemList.Builder setSelectedIndex(@IntRange(from=0) int);
+  }
+
+  public static interface ItemList.OnItemVisibilityChangedListener {
+    method public void onItemVisibilityChanged(int, int);
+  }
+
+  public static interface ItemList.OnSelectedListener {
+    method public void onSelected(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ListTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public java.util.List<androidx.car.app.model.SectionedItemList!> getSectionedLists();
+    method public androidx.car.app.model.ItemList? getSingleList();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder toBuilder();
+  }
+
+  public static final class ListTemplate.Builder {
+    ctor public ListTemplate.Builder();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.ListTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
+    method public androidx.car.app.model.ListTemplate build();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder clearSectionedLists();
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.ListTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.ListTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class LongMessageTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText getMessage();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public static final class LongMessageTemplate.Builder {
+    ctor public LongMessageTemplate.Builder(CharSequence);
+    method public androidx.car.app.model.LongMessageTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.LongMessageTemplate build();
+    method public androidx.car.app.model.LongMessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.LongMessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.LongMessageTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getDebugMessage();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public androidx.car.app.model.CarText getMessage();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public boolean isLoading();
+  }
+
+  public static final class MessageTemplate.Builder {
+    ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
+    ctor public MessageTemplate.Builder(CharSequence);
+    method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.MessageTemplate build();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
+    method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.MessageTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.MessageTemplate.Builder setIcon(androidx.car.app.model.CarIcon);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setLoading(boolean);
+    method @Deprecated public androidx.car.app.model.MessageTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Metadata {
+    method public androidx.car.app.model.Place? getPlace();
+    field public static final androidx.car.app.model.Metadata EMPTY_METADATA;
+  }
+
+  public static final class Metadata.Builder {
+    ctor public Metadata.Builder();
+    ctor public Metadata.Builder(androidx.car.app.model.Metadata);
+    method public androidx.car.app.model.Metadata build();
+    method public androidx.car.app.model.Metadata.Builder setPlace(androidx.car.app.model.Place);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnCheckedChangeDelegate {
+    method public void sendCheckedChange(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnClickDelegate {
+    method public boolean isParkedOnly();
+    method public void sendClick(androidx.car.app.OnDoneCallback);
+  }
+
+  public interface OnClickListener {
+    method public void onClick();
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface OnContentRefreshDelegate {
+    method public void sendContentRefreshRequested(androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public interface OnContentRefreshListener {
+    method public void onContentRefreshRequested();
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnItemVisibilityChangedDelegate {
+    method public void sendItemVisibilityChanged(int, int, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnSelectedDelegate {
+    method public void sendSelected(int, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Pane {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.CarIcon? getImage();
+    method public java.util.List<androidx.car.app.model.Row!> getRows();
+    method public boolean isLoading();
+  }
+
+  public static final class Pane.Builder {
+    ctor public Pane.Builder();
+    method public androidx.car.app.model.Pane.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Pane.Builder addRow(androidx.car.app.model.Row);
+    method public androidx.car.app.model.Pane build();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.Pane.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Pane.Builder setLoading(boolean);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PaneTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.Pane getPane();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class PaneTemplate.Builder {
+    ctor public PaneTemplate.Builder(androidx.car.app.model.Pane);
+    method public androidx.car.app.model.PaneTemplate build();
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.PaneTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ParkedOnlyOnClickListener implements androidx.car.app.model.OnClickListener {
+    method public static androidx.car.app.model.ParkedOnlyOnClickListener create(androidx.car.app.model.OnClickListener);
+    method public void onClick();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Place {
+    method public androidx.car.app.model.CarLocation getLocation();
+    method public androidx.car.app.model.PlaceMarker? getMarker();
+  }
+
+  public static final class Place.Builder {
+    ctor public Place.Builder(androidx.car.app.model.CarLocation);
+    ctor public Place.Builder(androidx.car.app.model.Place);
+    method public androidx.car.app.model.Place build();
+    method public androidx.car.app.model.Place.Builder setMarker(androidx.car.app.model.PlaceMarker);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceListMapTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Place? getAnchor();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.ItemList? getItemList();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.OnContentRefreshDelegate? getOnContentRefreshDelegate();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public boolean isCurrentLocationEnabled();
+    method public boolean isLoading();
+  }
+
+  public static final class PlaceListMapTemplate.Builder {
+    ctor public PlaceListMapTemplate.Builder();
+    method public androidx.car.app.model.PlaceListMapTemplate build();
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setAnchor(androidx.car.app.model.Place);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setCurrentLocationEnabled(boolean);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setLoading(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.PlaceListMapTemplate.Builder setOnContentRefreshListener(androidx.car.app.model.OnContentRefreshListener);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceMarker {
+    method public androidx.car.app.model.CarColor? getColor();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getIconType();
+    method public androidx.car.app.model.CarText? getLabel();
+    field public static final int TYPE_ICON = 0; // 0x0
+    field public static final int TYPE_IMAGE = 1; // 0x1
+  }
+
+  public static final class PlaceMarker.Builder {
+    ctor public PlaceMarker.Builder();
+    method public androidx.car.app.model.PlaceMarker build();
+    method public androidx.car.app.model.PlaceMarker.Builder setColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.PlaceMarker.Builder setIcon(androidx.car.app.model.CarIcon, int);
+    method public androidx.car.app.model.PlaceMarker.Builder setLabel(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Row implements androidx.car.app.model.Item {
+    method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.Metadata? getMetadata();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public int getNumericDecoration();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public int getRowImageType();
+    method public java.util.List<androidx.car.app.model.CarText!> getTexts();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public androidx.car.app.model.Toggle? getToggle();
+    method public boolean isBrowsable();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+    method public androidx.car.app.model.Row row();
+    method public CharSequence yourBoat();
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int IMAGE_TYPE_EXTRA_SMALL = 8; // 0x8
+    field public static final int IMAGE_TYPE_ICON = 4; // 0x4
+    field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
+    field public static final int IMAGE_TYPE_SMALL = 1; // 0x1
+    field public static final int NO_DECORATION = -1; // 0xffffffff
+  }
+
+  public static final class Row.Builder {
+    ctor public Row.Builder();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.Row.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Row.Builder addText(CharSequence);
+    method public androidx.car.app.model.Row build();
+    method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Row.Builder setEnabled(boolean);
+    method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon, int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder setIndexable(boolean);
+    method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
+    method @IntRange(from=0) @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.Row.Builder setNumericDecoration(int);
+    method public androidx.car.app.model.Row.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.Row.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Row.Builder setTitle(CharSequence);
+    method public androidx.car.app.model.Row.Builder setToggle(androidx.car.app.model.Toggle);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class RowSection extends androidx.car.app.model.Section<androidx.car.app.model.Row!> {
+    method public int getInitialSelectedIndex();
+    method public boolean isSelectionGroup();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
+    ctor public RowSection.Builder();
+    method public androidx.car.app.model.RowSection build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface SearchCallbackDelegate {
+    method public void sendSearchSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void sendSearchTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SearchTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public String? getInitialSearchText();
+    method public androidx.car.app.model.ItemList? getItemList();
+    method public androidx.car.app.model.SearchCallbackDelegate getSearchCallbackDelegate();
+    method public String? getSearchHint();
+    method public boolean isLoading();
+    method public boolean isShowKeyboardByDefault();
+  }
+
+  public static final class SearchTemplate.Builder {
+    ctor public SearchTemplate.Builder(androidx.car.app.model.SearchTemplate.SearchCallback);
+    method public androidx.car.app.model.SearchTemplate build();
+    method public androidx.car.app.model.SearchTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.SearchTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.SearchTemplate.Builder setInitialSearchText(String);
+    method public androidx.car.app.model.SearchTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method public androidx.car.app.model.SearchTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.SearchTemplate.Builder setSearchHint(String);
+    method public androidx.car.app.model.SearchTemplate.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  public static interface SearchTemplate.SearchCallback {
+    method public default void onSearchSubmitted(String);
+    method public default void onSearchTextChanged(String);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
+    ctor protected Section();
+    ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
+    method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
+    method public androidx.car.app.model.CarText? getNoItemsMessage();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
+    ctor protected Section.BaseBuilder();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setNoItemsMessage(androidx.car.app.model.CarText?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setNoItemsMessage(CharSequence?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setTitle(androidx.car.app.model.CarText?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setTitle(CharSequence?);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SectionedItemList {
+    method public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, CharSequence);
+    method public androidx.car.app.model.CarText getHeader();
+    method public androidx.car.app.model.ItemList getItemList();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class SectionedItemTemplate implements androidx.car.app.model.Template {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method public java.util.List<androidx.car.app.model.Section<? extends java.lang.Object!>!> getSections();
+    method public boolean isAlphabeticalIndexingAllowed();
+    method public boolean isLoading();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class SectionedItemTemplate.Builder {
+    ctor public SectionedItemTemplate.Builder();
+    ctor public SectionedItemTemplate.Builder(androidx.car.app.model.SectionedItemTemplate);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder addAction(androidx.car.app.model.Action);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder addSection(androidx.car.app.model.Section<? extends java.lang.Object!>);
+    method public androidx.car.app.model.SectionedItemTemplate build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder clearActions();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder clearSections();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setActions(java.util.List<androidx.car.app.model.Action!>);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setAlphabeticalIndexingAllowed(boolean);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setHeader(androidx.car.app.model.Header?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setLoading(boolean);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setSections(java.util.List<androidx.car.app.model.Section<? extends java.lang.Object!>!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public final class Tab implements androidx.car.app.model.Content {
+    method public String getContentId();
+    method public androidx.car.app.model.CarIcon getIcon();
+    method public androidx.car.app.model.CarText getTitle();
+  }
+
+  public static final class Tab.Builder {
+    ctor public Tab.Builder();
+    ctor public Tab.Builder(androidx.car.app.model.Tab);
+    method public androidx.car.app.model.Tab build();
+    method public androidx.car.app.model.Tab.Builder setContentId(String);
+    method public androidx.car.app.model.Tab.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Tab.Builder setTitle(CharSequence);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public interface TabCallbackDelegate {
+    method public void sendTabSelected(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class TabContents implements androidx.car.app.model.Content {
+    method public String getContentId();
+    method public androidx.car.app.model.Template getTemplate();
+    field public static final String CONTENT_ID = "TAB_CONTENTS_CONTENT_ID";
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class TabContents.Api8Builder {
+    ctor public TabContents.Api8Builder(androidx.car.app.model.Template);
+    method public androidx.car.app.model.TabContents build();
+  }
+
+  public static final class TabContents.Builder {
+    ctor public TabContents.Builder(androidx.car.app.model.Template);
+    method public androidx.car.app.model.TabContents build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class TabTemplate implements androidx.car.app.model.Template {
+    method public String getActiveTabContentId();
+    method public androidx.car.app.model.Action getHeaderAction();
+    method public androidx.car.app.model.TabCallbackDelegate getTabCallbackDelegate();
+    method public androidx.car.app.model.TabContents getTabContents();
+    method public java.util.List<androidx.car.app.model.Tab!> getTabs();
+    method public boolean isLoading();
+  }
+
+  public static final class TabTemplate.Builder {
+    ctor public TabTemplate.Builder(androidx.car.app.model.TabTemplate);
+    ctor public TabTemplate.Builder(androidx.car.app.model.TabTemplate.TabCallback);
+    method public androidx.car.app.model.TabTemplate.Builder addTab(androidx.car.app.model.Tab);
+    method public androidx.car.app.model.TabTemplate build();
+    method public androidx.car.app.model.TabTemplate.Builder setActiveTabContentId(String);
+    method public androidx.car.app.model.TabTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.TabTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.TabTemplate.Builder setTabContents(androidx.car.app.model.TabContents);
+  }
+
+  public static interface TabTemplate.TabCallback {
+    method public default void onTabSelected(String);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface Template {
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TemplateInfo {
+    ctor public TemplateInfo(Class<? extends androidx.car.app.model.Template!>, String);
+    method public Class<? extends androidx.car.app.model.Template!> getTemplateClass();
+    method public String getTemplateId();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TemplateWrapper {
+    method public static androidx.car.app.model.TemplateWrapper copyOf(androidx.car.app.model.TemplateWrapper);
+    method public int getCurrentTaskStep();
+    method public String getId();
+    method public androidx.car.app.model.Template getTemplate();
+    method public java.util.List<androidx.car.app.model.TemplateInfo!> getTemplateInfosForScreenStack();
+    method public boolean isRefresh();
+    method public void setCurrentTaskStep(int);
+    method public void setId(String);
+    method public void setRefresh(boolean);
+    method public void setTemplate(androidx.car.app.model.Template);
+    method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template);
+    method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template, String);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Toggle {
+    method public androidx.car.app.model.OnCheckedChangeDelegate getOnCheckedChangeDelegate();
+    method public boolean isChecked();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+  }
+
+  public static final class Toggle.Builder {
+    ctor public Toggle.Builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
+    method public androidx.car.app.model.Toggle build();
+    method public androidx.car.app.model.Toggle.Builder setChecked(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Toggle.Builder setEnabled(boolean);
+  }
+
+  public static interface Toggle.OnCheckedChangeListener {
+    method public void onCheckedChange(boolean);
+  }
+
+}
+
+package androidx.car.app.model.signin {
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class InputSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.CarText? getDefaultValue();
+    method public androidx.car.app.model.CarText? getErrorMessage();
+    method public androidx.car.app.model.CarText? getHint();
+    method public androidx.car.app.model.InputCallbackDelegate getInputCallbackDelegate();
+    method public int getInputType();
+    method public int getKeyboardType();
+    method public boolean isShowKeyboardByDefault();
+    field public static final int INPUT_TYPE_DEFAULT = 1; // 0x1
+    field public static final int INPUT_TYPE_PASSWORD = 2; // 0x2
+    field public static final int KEYBOARD_DEFAULT = 1; // 0x1
+    field public static final int KEYBOARD_EMAIL = 2; // 0x2
+    field public static final int KEYBOARD_NUMBER = 4; // 0x4
+    field public static final int KEYBOARD_PHONE = 3; // 0x3
+  }
+
+  public static final class InputSignInMethod.Builder {
+    ctor public InputSignInMethod.Builder(androidx.car.app.model.InputCallback);
+    method public androidx.car.app.model.signin.InputSignInMethod build();
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setDefaultValue(String);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setErrorMessage(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setHint(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setInputType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setKeyboardType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class PinSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public PinSignInMethod(CharSequence);
+    method public androidx.car.app.model.CarText getPinCode();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class ProviderSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public ProviderSignInMethod(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Action getAction();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(4) public final class QRCodeSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public QRCodeSignInMethod(android.net.Uri);
+    method public android.net.Uri getUri();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class SignInTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getAdditionalText();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText? getInstructions();
+    method public androidx.car.app.model.signin.SignInTemplate.SignInMethod getSignInMethod();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public static final class SignInTemplate.Builder {
+    ctor public SignInTemplate.Builder(androidx.car.app.model.signin.SignInTemplate.SignInMethod);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate build();
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setAdditionalText(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setInstructions(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setTitle(CharSequence);
+  }
+
+  public static interface SignInTemplate.SignInMethod {
+  }
+
+}
+
+package androidx.car.app.navigation {
+
+  public class NavigationManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void clearNavigationManagerCallback();
+    method @MainThread public void navigationEnded();
+    method @MainThread public void navigationStarted();
+    method @MainThread public void setNavigationManagerCallback(androidx.car.app.navigation.NavigationManagerCallback);
+    method @MainThread public void setNavigationManagerCallback(java.util.concurrent.Executor, androidx.car.app.navigation.NavigationManagerCallback);
+    method @MainThread public void updateTrip(androidx.car.app.navigation.model.Trip);
+  }
+
+  public interface NavigationManagerCallback {
+    method public default void onAutoDriveEnabled();
+    method public default void onStopNavigation();
+  }
+
+}
+
+package androidx.car.app.navigation.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Destination {
+    method public androidx.car.app.model.CarText? getAddress();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.CarText? getName();
+  }
+
+  public static final class Destination.Builder {
+    ctor public Destination.Builder();
+    method public androidx.car.app.navigation.model.Destination build();
+    method public androidx.car.app.navigation.model.Destination.Builder setAddress(CharSequence);
+    method public androidx.car.app.navigation.model.Destination.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Destination.Builder setName(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Lane {
+    method public java.util.List<androidx.car.app.navigation.model.LaneDirection!> getDirections();
+  }
+
+  public static final class Lane.Builder {
+    ctor public Lane.Builder();
+    method public androidx.car.app.navigation.model.Lane.Builder addDirection(androidx.car.app.navigation.model.LaneDirection);
+    method public androidx.car.app.navigation.model.Lane build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class LaneDirection {
+    method public static androidx.car.app.navigation.model.LaneDirection create(int, boolean);
+    method public int getShape();
+    method public boolean isRecommended();
+    field public static final int SHAPE_NORMAL_LEFT = 5; // 0x5
+    field public static final int SHAPE_NORMAL_RIGHT = 6; // 0x6
+    field public static final int SHAPE_SHARP_LEFT = 7; // 0x7
+    field public static final int SHAPE_SHARP_RIGHT = 8; // 0x8
+    field public static final int SHAPE_SLIGHT_LEFT = 3; // 0x3
+    field public static final int SHAPE_SLIGHT_RIGHT = 4; // 0x4
+    field public static final int SHAPE_STRAIGHT = 2; // 0x2
+    field public static final int SHAPE_UNKNOWN = 1; // 0x1
+    field public static final int SHAPE_U_TURN_LEFT = 9; // 0x9
+    field public static final int SHAPE_U_TURN_RIGHT = 10; // 0xa
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Maneuver {
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getRoundaboutExitAngle();
+    method public int getRoundaboutExitNumber();
+    method public int getType();
+    field public static final int TYPE_DEPART = 1; // 0x1
+    field public static final int TYPE_DESTINATION = 39; // 0x27
+    field public static final int TYPE_DESTINATION_LEFT = 41; // 0x29
+    field public static final int TYPE_DESTINATION_RIGHT = 42; // 0x2a
+    field public static final int TYPE_DESTINATION_STRAIGHT = 40; // 0x28
+    field public static final int TYPE_FERRY_BOAT = 37; // 0x25
+    field public static final int TYPE_FERRY_BOAT_LEFT = 47; // 0x2f
+    field public static final int TYPE_FERRY_BOAT_RIGHT = 48; // 0x30
+    field public static final int TYPE_FERRY_TRAIN = 38; // 0x26
+    field public static final int TYPE_FERRY_TRAIN_LEFT = 49; // 0x31
+    field public static final int TYPE_FERRY_TRAIN_RIGHT = 50; // 0x32
+    field public static final int TYPE_FORK_LEFT = 25; // 0x19
+    field public static final int TYPE_FORK_RIGHT = 26; // 0x1a
+    field public static final int TYPE_KEEP_LEFT = 3; // 0x3
+    field public static final int TYPE_KEEP_RIGHT = 4; // 0x4
+    field public static final int TYPE_MERGE_LEFT = 27; // 0x1b
+    field public static final int TYPE_MERGE_RIGHT = 28; // 0x1c
+    field public static final int TYPE_MERGE_SIDE_UNSPECIFIED = 29; // 0x1d
+    field public static final int TYPE_NAME_CHANGE = 2; // 0x2
+    field public static final int TYPE_OFF_RAMP_NORMAL_LEFT = 23; // 0x17
+    field public static final int TYPE_OFF_RAMP_NORMAL_RIGHT = 24; // 0x18
+    field public static final int TYPE_OFF_RAMP_SLIGHT_LEFT = 21; // 0x15
+    field public static final int TYPE_OFF_RAMP_SLIGHT_RIGHT = 22; // 0x16
+    field public static final int TYPE_ON_RAMP_NORMAL_LEFT = 15; // 0xf
+    field public static final int TYPE_ON_RAMP_NORMAL_RIGHT = 16; // 0x10
+    field public static final int TYPE_ON_RAMP_SHARP_LEFT = 17; // 0x11
+    field public static final int TYPE_ON_RAMP_SHARP_RIGHT = 18; // 0x12
+    field public static final int TYPE_ON_RAMP_SLIGHT_LEFT = 13; // 0xd
+    field public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14; // 0xe
+    field public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19; // 0x13
+    field public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20; // 0x14
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34; // 0x22
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35; // 0x23
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32; // 0x20
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33; // 0x21
+    field public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45; // 0x2d
+    field public static final int TYPE_ROUNDABOUT_ENTER_CW = 43; // 0x2b
+    field public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46; // 0x2e
+    field public static final int TYPE_ROUNDABOUT_EXIT_CW = 44; // 0x2c
+    field public static final int TYPE_STRAIGHT = 36; // 0x24
+    field public static final int TYPE_TURN_NORMAL_LEFT = 7; // 0x7
+    field public static final int TYPE_TURN_NORMAL_RIGHT = 8; // 0x8
+    field public static final int TYPE_TURN_SHARP_LEFT = 9; // 0x9
+    field public static final int TYPE_TURN_SHARP_RIGHT = 10; // 0xa
+    field public static final int TYPE_TURN_SLIGHT_LEFT = 5; // 0x5
+    field public static final int TYPE_TURN_SLIGHT_RIGHT = 6; // 0x6
+    field public static final int TYPE_UNKNOWN = 0; // 0x0
+    field public static final int TYPE_U_TURN_LEFT = 11; // 0xb
+    field public static final int TYPE_U_TURN_RIGHT = 12; // 0xc
+  }
+
+  public static final class Maneuver.Builder {
+    ctor public Maneuver.Builder(int);
+    method public androidx.car.app.navigation.model.Maneuver build();
+    method public androidx.car.app.navigation.model.Maneuver.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitAngle(@IntRange(from=1, to=360) int);
+    method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitNumber(@IntRange(from=1) int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class MapController {
+    method public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+  }
+
+  public static final class MapController.Builder {
+    ctor public MapController.Builder();
+    method public androidx.car.app.navigation.model.MapController build();
+    method public androidx.car.app.navigation.model.MapController.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.MapController.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class MapTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated public androidx.car.app.navigation.model.MapController? getMapController();
+    method @Deprecated public androidx.car.app.model.Pane? getPane();
+  }
+
+  @Deprecated public static final class MapTemplate.Builder {
+    ctor @Deprecated public MapTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setMapController(androidx.car.app.navigation.model.MapController);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setPane(androidx.car.app.model.Pane);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public final class MapWithContentTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Template getContentTemplate();
+    method public androidx.car.app.navigation.model.MapController? getMapController();
+  }
+
+  public static final class MapWithContentTemplate.Builder {
+    ctor public MapWithContentTemplate.Builder();
+    method public androidx.car.app.navigation.model.MapWithContentTemplate build();
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setContentTemplate(androidx.car.app.model.Template);
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setMapController(androidx.car.app.navigation.model.MapController);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.CarText? getText();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class MessageInfo.Builder {
+    ctor public MessageInfo.Builder(androidx.car.app.model.CarText);
+    ctor public MessageInfo.Builder(CharSequence);
+    method public androidx.car.app.navigation.model.MessageInfo build();
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setText(androidx.car.app.model.CarText);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setText(CharSequence);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class NavigationTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method public androidx.car.app.navigation.model.TravelEstimate? getDestinationTravelEstimate();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method public androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo? getNavigationInfo();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.Toggle? getPanModeToggle();
+  }
+
+  public static final class NavigationTemplate.Builder {
+    ctor public NavigationTemplate.Builder();
+    method public androidx.car.app.navigation.model.NavigationTemplate build();
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setDestinationTravelEstimate(androidx.car.app.navigation.model.TravelEstimate);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.NavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setNavigationInfo(androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.NavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+  }
+
+  public static interface NavigationTemplate.NavigationInfo {
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(2) public interface PanModeDelegate {
+    method public void sendPanModeChanged(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  public interface PanModeListener {
+    method public void onPanModeChanged(boolean);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceListNavigationTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method @Deprecated public androidx.car.app.model.OnContentRefreshDelegate? getOnContentRefreshDelegate();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @Deprecated public boolean isLoading();
+  }
+
+  @Deprecated public static final class PlaceListNavigationTemplate.Builder {
+    ctor @Deprecated public PlaceListNavigationTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setLoading(boolean);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setOnContentRefreshListener(androidx.car.app.model.OnContentRefreshListener);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(CharSequence);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class RoutePreviewNavigationTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method @Deprecated public androidx.car.app.model.Action? getNavigateAction();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @Deprecated public boolean isLoading();
+  }
+
+  @Deprecated public static final class RoutePreviewNavigationTemplate.Builder {
+    ctor @Deprecated public RoutePreviewNavigationTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setLoading(boolean);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setNavigateAction(androidx.car.app.model.Action);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+    method public androidx.car.app.model.Distance? getCurrentDistance();
+    method public androidx.car.app.navigation.model.Step? getCurrentStep();
+    method public androidx.car.app.model.CarIcon? getJunctionImage();
+    method public androidx.car.app.navigation.model.Step? getNextStep();
+    method public boolean isLoading();
+  }
+
+  public static final class RoutingInfo.Builder {
+    ctor public RoutingInfo.Builder();
+    method public androidx.car.app.navigation.model.RoutingInfo build();
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setCurrentStep(androidx.car.app.navigation.model.Step, androidx.car.app.model.Distance);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setJunctionImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setLoading(boolean);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setNextStep(androidx.car.app.navigation.model.Step);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Step {
+    method public androidx.car.app.model.CarText? getCue();
+    method public java.util.List<androidx.car.app.navigation.model.Lane!> getLanes();
+    method public androidx.car.app.model.CarIcon? getLanesImage();
+    method public androidx.car.app.navigation.model.Maneuver? getManeuver();
+    method public androidx.car.app.model.CarText? getRoad();
+  }
+
+  public static final class Step.Builder {
+    ctor public Step.Builder();
+    ctor public Step.Builder(androidx.car.app.model.CarText);
+    ctor public Step.Builder(CharSequence);
+    method public androidx.car.app.navigation.model.Step.Builder addLane(androidx.car.app.navigation.model.Lane);
+    method public androidx.car.app.navigation.model.Step build();
+    method public androidx.car.app.navigation.model.Step.Builder setCue(CharSequence);
+    method public androidx.car.app.navigation.model.Step.Builder setLanesImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Step.Builder setManeuver(androidx.car.app.navigation.model.Maneuver);
+    method public androidx.car.app.navigation.model.Step.Builder setRoad(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TravelEstimate {
+    method public androidx.car.app.model.DateTimeWithZone? getArrivalTimeAtDestination();
+    method public androidx.car.app.model.Distance? getRemainingDistance();
+    method public androidx.car.app.model.CarColor? getRemainingDistanceColor();
+    method public androidx.car.app.model.CarColor? getRemainingTimeColor();
+    method public long getRemainingTimeSeconds();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.CarIcon? getTripIcon();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.CarText? getTripText();
+    field public static final long REMAINING_TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+  }
+
+  public static final class TravelEstimate.Builder {
+    ctor public TravelEstimate.Builder(androidx.car.app.model.Distance, androidx.car.app.model.DateTimeWithZone);
+    ctor @RequiresApi(26) public TravelEstimate.Builder(androidx.car.app.model.Distance, java.time.ZonedDateTime);
+    method public androidx.car.app.navigation.model.TravelEstimate build();
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingDistanceColor(androidx.car.app.model.CarColor);
+    method @RequiresApi(26) public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTime(java.time.Duration);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeSeconds(@IntRange(from=0xffffffff) long);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.TravelEstimate.Builder setTripIcon(androidx.car.app.model.CarIcon);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.TravelEstimate.Builder setTripText(androidx.car.app.model.CarText);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Trip {
+    method public androidx.car.app.model.CarText? getCurrentRoad();
+    method public java.util.List<androidx.car.app.navigation.model.TravelEstimate!> getDestinationTravelEstimates();
+    method public java.util.List<androidx.car.app.navigation.model.Destination!> getDestinations();
+    method public java.util.List<androidx.car.app.navigation.model.TravelEstimate!> getStepTravelEstimates();
+    method public java.util.List<androidx.car.app.navigation.model.Step!> getSteps();
+    method public boolean isLoading();
+  }
+
+  public static final class Trip.Builder {
+    ctor public Trip.Builder();
+    method public androidx.car.app.navigation.model.Trip.Builder addDestination(androidx.car.app.navigation.model.Destination, androidx.car.app.navigation.model.TravelEstimate);
+    method public androidx.car.app.navigation.model.Trip.Builder addStep(androidx.car.app.navigation.model.Step, androidx.car.app.navigation.model.TravelEstimate);
+    method public androidx.car.app.navigation.model.Trip build();
+    method public androidx.car.app.navigation.model.Trip.Builder setCurrentRoad(CharSequence);
+    method public androidx.car.app.navigation.model.Trip.Builder setLoading(boolean);
+  }
+
+}
+
+package androidx.car.app.notification {
+
+  public final class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
+    ctor public CarAppExtender(android.app.Notification);
+    method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+    method public java.util.List<android.app.Notification.Action!> getActions();
+    method public String? getChannelId();
+    method public androidx.car.app.model.CarColor? getColor();
+    method public android.app.PendingIntent? getContentIntent();
+    method public CharSequence? getContentText();
+    method public CharSequence? getContentTitle();
+    method public android.app.PendingIntent? getDeleteIntent();
+    method public int getImportance();
+    method public android.graphics.Bitmap? getLargeIcon();
+    method @DrawableRes public int getSmallIcon();
+    method public static boolean isExtended(android.app.Notification);
+  }
+
+  public static final class CarAppExtender.Builder {
+    ctor public CarAppExtender.Builder();
+    method public androidx.car.app.notification.CarAppExtender.Builder addAction(@DrawableRes int, CharSequence, android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender build();
+    method public androidx.car.app.notification.CarAppExtender.Builder setChannelId(String);
+    method public androidx.car.app.notification.CarAppExtender.Builder setColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentIntent(android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentText(CharSequence);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentTitle(CharSequence);
+    method public androidx.car.app.notification.CarAppExtender.Builder setDeleteIntent(android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender.Builder setImportance(int);
+    method public androidx.car.app.notification.CarAppExtender.Builder setLargeIcon(android.graphics.Bitmap);
+    method public androidx.car.app.notification.CarAppExtender.Builder setSmallIcon(int);
+  }
+
+  public final class CarNotificationManager {
+    method public boolean areNotificationsEnabled();
+    method public void cancel(int);
+    method public void cancel(String?, int);
+    method public void cancelAll();
+    method public void createNotificationChannel(androidx.core.app.NotificationChannelCompat);
+    method public void createNotificationChannelGroup(androidx.core.app.NotificationChannelGroupCompat);
+    method public void createNotificationChannelGroups(java.util.List<androidx.core.app.NotificationChannelGroupCompat!>);
+    method public void createNotificationChannels(java.util.List<androidx.core.app.NotificationChannelCompat!>);
+    method public void deleteNotificationChannel(String);
+    method public void deleteNotificationChannelGroup(String);
+    method public void deleteUnlistedNotificationChannels(java.util.Collection<java.lang.String!>);
+    method public static androidx.car.app.notification.CarNotificationManager from(android.content.Context);
+    method public static java.util.Set<java.lang.String!> getEnabledListenerPackages(android.content.Context);
+    method public int getImportance();
+    method public androidx.core.app.NotificationChannelCompat? getNotificationChannel(String);
+    method public androidx.core.app.NotificationChannelCompat? getNotificationChannel(String, String);
+    method public androidx.core.app.NotificationChannelGroupCompat? getNotificationChannelGroup(String);
+    method public java.util.List<androidx.core.app.NotificationChannelGroupCompat!> getNotificationChannelGroups();
+    method public java.util.List<androidx.core.app.NotificationChannelCompat!> getNotificationChannels();
+    method public void notify(int, androidx.core.app.NotificationCompat.Builder);
+    method public void notify(String?, int, androidx.core.app.NotificationCompat.Builder);
+  }
+
+  public final class CarPendingIntent {
+    method public static android.app.PendingIntent getCarApp(android.content.Context, int, android.content.Intent, int);
+  }
+
+}
+
+package androidx.car.app.serialization {
+
+  public final class Bundleable implements android.os.Parcelable {
+    method public static androidx.car.app.serialization.Bundleable create(Object) throws androidx.car.app.serialization.BundlerException;
+    method public int describeContents();
+    method public Object get() throws androidx.car.app.serialization.BundlerException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.car.app.serialization.Bundleable!> CREATOR;
+  }
+
+  public class BundlerException extends java.lang.Exception {
+    ctor public BundlerException(String?);
+    ctor public BundlerException(String?, Throwable);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+    method public int getSize();
+    method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+    property public abstract int size;
+  }
+
+}
+
+package androidx.car.app.suggestion {
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public class SuggestionManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void updateSuggestions(java.util.List<androidx.car.app.suggestion.model.Suggestion!>);
+  }
+
+}
+
+package androidx.car.app.suggestion.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Suggestion {
+    method public android.app.PendingIntent? getAction();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getIdentifier();
+    method public androidx.car.app.model.CarText? getSubtitle();
+    method public androidx.car.app.model.CarText getTitle();
+  }
+
+  public static final class Suggestion.Builder {
+    ctor public Suggestion.Builder();
+    method public androidx.car.app.suggestion.model.Suggestion build();
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setAction(android.app.PendingIntent);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setIdentifier(String);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setSubtitle(CharSequence);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setTitle(CharSequence);
+  }
+
+}
+
+package androidx.car.app.validation {
+
+  public final class HostValidator {
+    method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getAllowedHosts();
+    method public boolean isValidHost(androidx.car.app.HostInfo);
+    field public static final androidx.car.app.validation.HostValidator ALLOW_ALL_HOSTS_VALIDATOR;
+    field public static final String TEMPLATE_RENDERER_PERMISSION = "android.car.permission.TEMPLATE_RENDERER";
+  }
+
+  public static final class HostValidator.Builder {
+    ctor public HostValidator.Builder(android.content.Context);
+    method public androidx.car.app.validation.HostValidator.Builder addAllowedHost(String, String);
+    method public androidx.car.app.validation.HostValidator.Builder addAllowedHosts(@ArrayRes int);
+    method public androidx.car.app.validation.HostValidator build();
+  }
+
+}
+
+package androidx.car.app.versioning {
+
+  public final class CarAppApiLevels {
+    method public static int getLatest();
+    method public static int getOldest();
+    field public static final int LEVEL_1 = 1; // 0x1
+    field public static final int LEVEL_2 = 2; // 0x2
+    field public static final int LEVEL_3 = 3; // 0x3
+    field public static final int LEVEL_4 = 4; // 0x4
+    field public static final int LEVEL_5 = 5; // 0x5
+    field public static final int LEVEL_6 = 6; // 0x6
+    field public static final int LEVEL_7 = 7; // 0x7
+    field public static final int LEVEL_8 = 8; // 0x8
+  }
+
+}
+
diff --git a/car/app/app/api/res-1.7.0-beta02.txt b/car/app/app/api/res-1.7.0-beta02.txt
new file mode 100644
index 0000000..686fc80
--- /dev/null
+++ b/car/app/app/api/res-1.7.0-beta02.txt
@@ -0,0 +1,5 @@
+attr carColorPrimary
+attr carColorPrimaryDark
+attr carColorSecondary
+attr carColorSecondaryDark
+attr carPermissionActivityLayout
diff --git a/car/app/app/api/restricted_1.7.0-beta02.txt b/car/app/app/api/restricted_1.7.0-beta02.txt
new file mode 100644
index 0000000..63d6309
--- /dev/null
+++ b/car/app/app/api/restricted_1.7.0-beta02.txt
@@ -0,0 +1,2456 @@
+// Signature format: 4.0
+package androidx.car.app {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class AppInfo {
+    ctor @VisibleForTesting public AppInfo(int, int, String);
+    method public int getLatestCarAppApiLevel();
+    method public String getLibraryDisplayVersion();
+    method public int getMinCarAppApiLevel();
+    field public static final String MIN_API_LEVEL_METADATA_KEY = "androidx.car.app.minCarApiLevel";
+  }
+
+  public class AppManager implements androidx.car.app.managers.Manager {
+    method @androidx.car.app.annotations.RequiresCarApi(5) public void dismissAlert(int);
+    method public void invalidate();
+    method public void setSurfaceCallback(androidx.car.app.SurfaceCallback?);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public void showAlert(androidx.car.app.model.Alert);
+    method public void showToast(CharSequence, int);
+  }
+
+  public final class CarAppPermission {
+    method public static void checkHasLibraryPermission(android.content.Context, String);
+    method public static void checkHasPermission(android.content.Context, String);
+    field public static final String ACCESS_SURFACE = "androidx.car.app.ACCESS_SURFACE";
+    field public static final String MAP_TEMPLATES = "androidx.car.app.MAP_TEMPLATES";
+    field public static final String NAVIGATION_TEMPLATES = "androidx.car.app.NAVIGATION_TEMPLATES";
+  }
+
+  public abstract class CarAppService extends android.app.Service {
+    ctor public CarAppService();
+    method public abstract androidx.car.app.validation.HostValidator createHostValidator();
+    method @CallSuper public final void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
+    method @Deprecated public final androidx.car.app.Session? getCurrentSession();
+    method public final androidx.car.app.HostInfo? getHostInfo();
+    method public final androidx.car.app.Session? getSession(androidx.car.app.SessionInfo);
+    method @CallSuper public final android.os.IBinder onBind(android.content.Intent);
+    method public androidx.car.app.Session onCreateSession();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.Session onCreateSession(androidx.car.app.SessionInfo);
+    method public final boolean onUnbind(android.content.Intent);
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_CALLING_APP = "androidx.car.app.category.CALLING";
+    field @Deprecated public static final String CATEGORY_CHARGING_APP = "androidx.car.app.category.CHARGING";
+    field @androidx.car.app.annotations.RequiresCarApi(6) public static final String CATEGORY_FEATURE_CLUSTER = "androidx.car.app.category.FEATURE_CLUSTER";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_IOT_APP = "androidx.car.app.category.IOT";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_MESSAGING_APP = "androidx.car.app.category.MESSAGING";
+    field public static final String CATEGORY_NAVIGATION_APP = "androidx.car.app.category.NAVIGATION";
+    field @Deprecated public static final String CATEGORY_PARKING_APP = "androidx.car.app.category.PARKING";
+    field public static final String CATEGORY_POI_APP = "androidx.car.app.category.POI";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_SETTINGS_APP = "androidx.car.app.category.SETTINGS";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String CATEGORY_WEATHER_APP = "androidx.car.app.category.WEATHER";
+    field public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
+  }
+
+  public class CarContext extends android.content.ContextWrapper {
+    method public void finishCarApp();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public android.content.ComponentName? getCallingComponent();
+    method public int getCarAppApiLevel();
+    method public <T> T getCarService(Class<T!>);
+    method public Object getCarService(String);
+    method public String getCarServiceName(Class<? extends java.lang.Object!>);
+    method public androidx.car.app.HostInfo? getHostInfo();
+    method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+    method public boolean isDarkMode();
+    method public void requestPermissions(java.util.List<java.lang.String!>, androidx.car.app.OnRequestPermissionsListener);
+    method public void requestPermissions(java.util.List<java.lang.String!>, java.util.concurrent.Executor, androidx.car.app.OnRequestPermissionsListener);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public void setCarAppResult(int, android.content.Intent?);
+    method public void startCarApp(android.content.Intent);
+    method @Deprecated public static void startCarApp(android.content.Intent, android.content.Intent);
+    field public static final String ACTION_NAVIGATE = "androidx.car.app.action.NAVIGATE";
+    field public static final String APP_SERVICE = "app";
+    field public static final String CAR_SERVICE = "car";
+    field @androidx.car.app.annotations.RequiresCarApi(2) public static final String CONSTRAINT_SERVICE = "constraints";
+    field public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra.START_CAR_APP_BINDER_KEY";
+    field @androidx.car.app.annotations.RequiresCarApi(3) public static final String HARDWARE_SERVICE = "hardware";
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final String MEDIA_PLAYBACK_SERVICE = "media_playback";
+    field public static final String NAVIGATION_SERVICE = "navigation";
+    field public static final String SCREEN_SERVICE = "screen";
+    field public static final String SUGGESTION_SERVICE = "suggestion";
+  }
+
+  public final class CarToast {
+    method public static androidx.car.app.CarToast makeText(androidx.car.app.CarContext, @StringRes int, int);
+    method public static androidx.car.app.CarToast makeText(androidx.car.app.CarContext, CharSequence, int);
+    method public void setDuration(int);
+    method public void setText(@StringRes int);
+    method public void setText(CharSequence);
+    method public void show();
+    field public static final int LENGTH_LONG = 1; // 0x1
+    field public static final int LENGTH_SHORT = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class FailureResponse {
+    ctor public FailureResponse(Throwable);
+    method public int getErrorType();
+    method public String getStackTrace();
+    field public static final int BUNDLER_EXCEPTION = 1; // 0x1
+    field public static final int ILLEGAL_STATE_EXCEPTION = 2; // 0x2
+    field public static final int INVALID_PARAMETER_EXCEPTION = 3; // 0x3
+    field public static final int REMOTE_EXCEPTION = 6; // 0x6
+    field public static final int RUNTIME_EXCEPTION = 5; // 0x5
+    field public static final int SECURITY_EXCEPTION = 4; // 0x4
+    field public static final int UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class HandshakeInfo {
+    ctor public HandshakeInfo(String, int);
+    method public int getHostCarAppApiLevel();
+    method public String getHostPackageName();
+  }
+
+  public final class HostException extends java.lang.RuntimeException {
+    ctor public HostException(String);
+    ctor public HostException(String, Throwable);
+    ctor public HostException(Throwable);
+  }
+
+  public final class HostInfo {
+    ctor public HostInfo(String, int);
+    method public String getPackageName();
+    method public int getUid();
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnDoneCallback {
+    method public default void onFailure(androidx.car.app.serialization.Bundleable);
+    method public default void onSuccess(androidx.car.app.serialization.Bundleable?);
+  }
+
+  public interface OnRequestPermissionsListener {
+    method public void onRequestPermissionsResult(java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
+  }
+
+  public interface OnScreenResultListener {
+    method public void onScreenResult(Object?);
+  }
+
+  public abstract class Screen implements androidx.lifecycle.LifecycleOwner {
+    ctor protected Screen(androidx.car.app.CarContext);
+    method public final void finish();
+    method public final androidx.car.app.CarContext getCarContext();
+    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method public String? getMarker();
+    method public final androidx.car.app.ScreenManager getScreenManager();
+    method public final void invalidate();
+    method public abstract androidx.car.app.model.Template onGetTemplate();
+    method public void setMarker(String?);
+    method public void setResult(Object?);
+  }
+
+  @MainThread public class ScreenManager implements androidx.car.app.managers.Manager {
+    method public java.util.Collection<androidx.car.app.Screen!> getScreenStack();
+    method public int getStackSize();
+    method public androidx.car.app.Screen getTop();
+    method public void pop();
+    method public void popTo(String);
+    method public void popToRoot();
+    method public void push(androidx.car.app.Screen);
+    method public void pushForResult(androidx.car.app.Screen, androidx.car.app.OnScreenResultListener);
+    method public void remove(androidx.car.app.Screen);
+  }
+
+  public abstract class Session implements androidx.lifecycle.LifecycleOwner {
+    ctor public Session();
+    method public final androidx.car.app.CarContext getCarContext();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public void onCarConfigurationChanged(android.content.res.Configuration);
+    method public abstract androidx.car.app.Screen onCreateScreen(android.content.Intent);
+    method public void onNewIntent(android.content.Intent);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class SessionInfo {
+    ctor public SessionInfo(int, String);
+    method public int getDisplayType();
+    method public String getSessionId();
+    method public java.util.Set<java.lang.Class<? extends androidx.car.app.model.Template!>!>? getSupportedTemplates(int);
+    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
+  }
+
+  public class SessionInfoIntentEncoder {
+    method public static boolean containsSessionInfo(android.content.Intent);
+    method public static androidx.car.app.SessionInfo decode(android.content.Intent);
+    method public static void encode(androidx.car.app.SessionInfo, android.content.Intent);
+  }
+
+  public interface SurfaceCallback {
+    method @androidx.car.app.annotations.RequiresCarApi(5) public default void onClick(float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onFling(float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onScale(float, float, float);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public default void onScroll(float, float);
+    method public default void onStableAreaChanged(android.graphics.Rect);
+    method public default void onSurfaceAvailable(androidx.car.app.SurfaceContainer);
+    method public default void onSurfaceDestroyed(androidx.car.app.SurfaceContainer);
+    method public default void onVisibleAreaChanged(android.graphics.Rect);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SurfaceContainer {
+    ctor public SurfaceContainer(android.view.Surface?, int, int, int);
+    method public int getDpi();
+    method public int getHeight();
+    method public android.view.Surface? getSurface();
+    method public int getWidth();
+  }
+
+}
+
+package androidx.car.app.annotations {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface CarProtocol {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalCarApi {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface RequiresCarApi {
+    method public abstract int value();
+  }
+
+}
+
+package androidx.car.app.connection {
+
+  public final class CarConnection {
+    ctor @MainThread public CarConnection(android.content.Context);
+    method public androidx.lifecycle.LiveData<java.lang.Integer!> getType();
+    field public static final String ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED";
+    field public static final String CAR_CONNECTION_STATE = "CarConnectionState";
+    field public static final int CONNECTION_TYPE_NATIVE = 1; // 0x1
+    field public static final int CONNECTION_TYPE_NOT_CONNECTED = 0; // 0x0
+    field public static final int CONNECTION_TYPE_PROJECTION = 2; // 0x2
+  }
+
+}
+
+package androidx.car.app.constraints {
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public class ConstraintManager implements androidx.car.app.managers.Manager {
+    method public int getContentLimit(int);
+    method @androidx.car.app.annotations.RequiresCarApi(6) public boolean isAppDrivenRefreshEnabled();
+    field public static final int CONTENT_LIMIT_TYPE_GRID = 1; // 0x1
+    field public static final int CONTENT_LIMIT_TYPE_LIST = 0; // 0x0
+    field public static final int CONTENT_LIMIT_TYPE_PANE = 4; // 0x4
+    field public static final int CONTENT_LIMIT_TYPE_PLACE_LIST = 2; // 0x2
+    field public static final int CONTENT_LIMIT_TYPE_ROUTE_LIST = 3; // 0x3
+  }
+
+}
+
+package androidx.car.app.features {
+
+  public final class CarFeatures {
+    method public static boolean isFeatureEnabled(android.content.Context, String);
+    field public static final String FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING = "background_audio_while_driving";
+  }
+
+}
+
+package androidx.car.app.hardware {
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarHardwareManager extends androidx.car.app.managers.Manager {
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public default androidx.car.app.hardware.climate.CarClimate getCarClimate();
+    method public default androidx.car.app.hardware.info.CarInfo getCarInfo();
+    method public default androidx.car.app.hardware.info.CarSensors getCarSensors();
+  }
+
+}
+
+package androidx.car.app.hardware.climate {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CabinTemperatureProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Float!,java.lang.Float!>!> getCarZoneSetsToCabinCelsiusTemperatureRanges();
+    method public float getCelsiusSupportedIncrement();
+    method public float getFahrenheitSupportedIncrement();
+    method public android.util.Pair<java.lang.Float!,java.lang.Float!> getSupportedMinMaxCelsiusRange();
+    method public android.util.Pair<java.lang.Float!,java.lang.Float!> getSupportedMinMaxFahrenheitRange();
+    method public boolean hasCarZoneSetsToCabinCelsiusTemperatureRanges();
+    method public boolean hasCelsiusSupportedIncrement();
+    method public boolean hasFahrenheitSupportedIncrement();
+    method public boolean hasSupportedMinMaxCelsiusRange();
+    method public boolean hasSupportedMinMaxFahrenheitRange();
+  }
+
+  public static final class CabinTemperatureProfile.Builder {
+    ctor public CabinTemperatureProfile.Builder();
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile build();
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setCarZoneSetsToCabinCelsiusTemperatureRanges(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Float!,java.lang.Float!>!>);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setCelsiusSupportedIncrement(float);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setFahrenheitSupportedIncrement(float);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setSupportedMinMaxCelsiusRange(android.util.Pair<java.lang.Float!,java.lang.Float!>);
+    method public androidx.car.app.hardware.climate.CabinTemperatureProfile.Builder setSupportedMinMaxFahrenheitRange(android.util.Pair<java.lang.Float!,java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @MainThread @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimate {
+    method public void fetchClimateProfile(java.util.concurrent.Executor, androidx.car.app.hardware.climate.ClimateProfileRequest, androidx.car.app.hardware.climate.CarClimateProfileCallback);
+    method public void registerClimateStateCallback(java.util.concurrent.Executor, androidx.car.app.hardware.climate.RegisterClimateStateRequest, androidx.car.app.hardware.climate.CarClimateStateCallback);
+    method public <E> void setClimateState(java.util.concurrent.Executor, androidx.car.app.hardware.climate.ClimateStateRequest<E!>, androidx.car.app.hardware.common.CarSetOperationStatusCallback);
+    method public void unregisterClimateStateCallback(androidx.car.app.hardware.climate.CarClimateStateCallback);
+  }
+
+  @SuppressCompatibility @MainThread @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class CarClimateFeature {
+    method public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getFeature();
+  }
+
+  public static final class CarClimateFeature.Builder {
+    ctor public CarClimateFeature.Builder(int);
+    method public androidx.car.app.hardware.climate.CarClimateFeature.Builder addCarZones(androidx.car.app.hardware.common.CarZone!...);
+    method public androidx.car.app.hardware.climate.CarClimateFeature build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimateProfileCallback {
+    method public default void onCabinTemperatureProfileAvailable(androidx.car.app.hardware.climate.CabinTemperatureProfile);
+    method public default void onCarZoneMappingInfoProfileAvailable(androidx.car.app.hardware.climate.CarZoneMappingInfoProfile);
+    method public default void onDefrosterProfileAvailable(androidx.car.app.hardware.climate.DefrosterProfile);
+    method public default void onElectricDefrosterProfileAvailable(androidx.car.app.hardware.climate.ElectricDefrosterProfile);
+    method public default void onFanDirectionProfileAvailable(androidx.car.app.hardware.climate.FanDirectionProfile);
+    method public default void onFanSpeedLevelProfileAvailable(androidx.car.app.hardware.climate.FanSpeedLevelProfile);
+    method public default void onHvacAcProfileAvailable(androidx.car.app.hardware.climate.HvacAcProfile);
+    method public default void onHvacAutoModeProfileAvailable(androidx.car.app.hardware.climate.HvacAutoModeProfile);
+    method public default void onHvacAutoRecirculationProfileAvailable(androidx.car.app.hardware.climate.HvacAutoRecirculationProfile);
+    method public default void onHvacDualModeProfileAvailable(androidx.car.app.hardware.climate.HvacDualModeProfile);
+    method public default void onHvacMaxAcModeProfileAvailable(androidx.car.app.hardware.climate.HvacMaxAcModeProfile);
+    method public default void onHvacPowerProfileAvailable(androidx.car.app.hardware.climate.HvacPowerProfile);
+    method public default void onHvacRecirculationProfileAvailable(androidx.car.app.hardware.climate.HvacRecirculationProfile);
+    method public default void onMaxDefrosterProfileAvailable(androidx.car.app.hardware.climate.MaxDefrosterProfile);
+    method public default void onSeatTemperatureLevelProfileAvailable(androidx.car.app.hardware.climate.SeatTemperatureProfile);
+    method public default void onSeatVentilationLevelProfileAvailable(androidx.car.app.hardware.climate.SeatVentilationProfile);
+    method public default void onSteeringWheelHeatProfileAvailable(androidx.car.app.hardware.climate.SteeringWheelHeatProfile);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarClimateStateCallback {
+    method public default void onCabinTemperatureStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public default void onDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onElectricDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onFanDirectionStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onFanSpeedLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onHvacAcStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacAutoModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacAutoRecirculationStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacDualModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacMaxAcModeStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacPowerStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onHvacRecirculationStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onMaxDefrosterStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public default void onSeatTemperatureLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onSeatVentilationLevelStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public default void onSteeringWheelHeatStateAvailable(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class CarZoneMappingInfoProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class CarZoneMappingInfoProfile.Builder {
+    ctor public CarZoneMappingInfoProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.CarZoneMappingInfoProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class ClimateProfileRequest {
+    method public java.util.Set<java.lang.Integer!> getAllClimateProfiles();
+    method public java.util.List<androidx.car.app.hardware.climate.CarClimateFeature!> getClimateProfileFeatures();
+    field public static final int FEATURE_CABIN_TEMPERATURE = 4; // 0x4
+    field public static final int FEATURE_CAR_ZONE_MAPPING = 17; // 0x11
+    field public static final int FEATURE_FAN_DIRECTION = 6; // 0x6
+    field public static final int FEATURE_FAN_SPEED = 5; // 0x5
+    field public static final int FEATURE_HVAC_AC = 2; // 0x2
+    field public static final int FEATURE_HVAC_AUTO_MODE = 12; // 0xc
+    field public static final int FEATURE_HVAC_AUTO_RECIRCULATION = 11; // 0xb
+    field public static final int FEATURE_HVAC_DEFROSTER = 14; // 0xe
+    field public static final int FEATURE_HVAC_DUAL_MODE = 13; // 0xd
+    field public static final int FEATURE_HVAC_ELECTRIC_DEFROSTER = 16; // 0x10
+    field public static final int FEATURE_HVAC_MAX_AC = 3; // 0x3
+    field public static final int FEATURE_HVAC_MAX_DEFROSTER = 15; // 0xf
+    field public static final int FEATURE_HVAC_POWER = 1; // 0x1
+    field public static final int FEATURE_HVAC_RECIRCULATION = 10; // 0xa
+    field public static final int FEATURE_SEAT_TEMPERATURE_LEVEL = 7; // 0x7
+    field public static final int FEATURE_SEAT_VENTILATION_LEVEL = 8; // 0x8
+    field public static final int FEATURE_STEERING_WHEEL_HEAT = 9; // 0x9
+  }
+
+  public static final class ClimateProfileRequest.Builder {
+    ctor public ClimateProfileRequest.Builder();
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest.Builder addClimateProfileFeatures(androidx.car.app.hardware.climate.CarClimateFeature!...);
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest build();
+    method public androidx.car.app.hardware.climate.ClimateProfileRequest.Builder setAllClimateProfiles();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class ClimateStateRequest<T> {
+    method public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getRequestedFeature();
+    method public T getRequestedValue();
+  }
+
+  public static final class ClimateStateRequest.Builder<T> {
+    ctor public ClimateStateRequest.Builder(int, T!);
+    method public androidx.car.app.hardware.climate.ClimateStateRequest.Builder<T!> addCarZones(androidx.car.app.hardware.common.CarZone);
+    method public androidx.car.app.hardware.climate.ClimateStateRequest<T!> build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class DefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class DefrosterProfile.Builder {
+    ctor public DefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.DefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class ElectricDefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class ElectricDefrosterProfile.Builder {
+    ctor public ElectricDefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.ElectricDefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class FanDirectionProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,java.util.Set<java.lang.Integer!>!> getCarZoneSetsToFanDirectionValues();
+  }
+
+  public static final class FanDirectionProfile.Builder {
+    ctor public FanDirectionProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,java.util.Set<java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.FanDirectionProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class FanSpeedLevelProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToFanSpeedLevelRanges();
+  }
+
+  public static final class FanSpeedLevelProfile.Builder {
+    ctor public FanSpeedLevelProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.FanSpeedLevelProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAcProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAcProfile.Builder {
+    ctor public HvacAcProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAcProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAutoModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAutoModeProfile.Builder {
+    ctor public HvacAutoModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAutoModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacAutoRecirculationProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacAutoRecirculationProfile.Builder {
+    ctor public HvacAutoRecirculationProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacAutoRecirculationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacDualModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacDualModeProfile.Builder {
+    ctor public HvacDualModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacDualModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacMaxAcModeProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacMaxAcModeProfile.Builder {
+    ctor public HvacMaxAcModeProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacMaxAcModeProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacPowerProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class HvacPowerProfile.Builder {
+    ctor public HvacPowerProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacPowerProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class HvacRecirculationProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZones();
+  }
+
+  public static final class HvacRecirculationProfile.Builder {
+    ctor public HvacRecirculationProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.HvacRecirculationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class MaxDefrosterProfile {
+    method public java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!> getSupportedCarZoneSets();
+  }
+
+  public static final class MaxDefrosterProfile.Builder {
+    ctor public MaxDefrosterProfile.Builder(java.util.List<java.util.Set<androidx.car.app.hardware.common.CarZone!>!>);
+    method public androidx.car.app.hardware.climate.MaxDefrosterProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class RegisterClimateStateRequest {
+    method public java.util.List<androidx.car.app.hardware.climate.CarClimateFeature!> getClimateRegisterFeatures();
+  }
+
+  public static final class RegisterClimateStateRequest.Builder {
+    ctor public RegisterClimateStateRequest.Builder(boolean);
+    method public androidx.car.app.hardware.climate.RegisterClimateStateRequest.Builder addClimateRegisterFeatures(androidx.car.app.hardware.climate.CarClimateFeature!...);
+    method public androidx.car.app.hardware.climate.RegisterClimateStateRequest build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SeatTemperatureProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSeatTemperatureValues();
+  }
+
+  public static final class SeatTemperatureProfile.Builder {
+    ctor public SeatTemperatureProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SeatTemperatureProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SeatVentilationProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSeatVentilationValues();
+  }
+
+  public static final class SeatVentilationProfile.Builder {
+    ctor public SeatVentilationProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SeatVentilationProfile build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class SteeringWheelHeatProfile {
+    method public java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!> getCarZoneSetsToSteeringWheelHeatValues();
+  }
+
+  public static final class SteeringWheelHeatProfile.Builder {
+    ctor public SteeringWheelHeatProfile.Builder(java.util.Map<java.util.Set<androidx.car.app.hardware.common.CarZone!>!,android.util.Pair<java.lang.Integer!,java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.climate.SteeringWheelHeatProfile build();
+  }
+
+}
+
+package androidx.car.app.hardware.common {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public interface CarSetOperationStatusCallback {
+    method public default void onSetCarClimateStateCabinTemperature(int);
+    method public default void onSetCarClimateStateDefroster(int);
+    method public default void onSetCarClimateStateElectricDefroster(int);
+    method public default void onSetCarClimateStateFanDirection(int);
+    method public default void onSetCarClimateStateFanSpeedLevel(int);
+    method public default void onSetCarClimateStateHvacAc(int);
+    method public default void onSetCarClimateStateHvacAutoMode(int);
+    method public default void onSetCarClimateStateHvacAutoRecirculation(int);
+    method public default void onSetCarClimateStateHvacDualMode(int);
+    method public default void onSetCarClimateStateHvacMaxAcMode(int);
+    method public default void onSetCarClimateStateHvacPower(int);
+    method public default void onSetCarClimateStateHvacRecirculation(int);
+    method public default void onSetCarClimateStateMaxDefroster(int);
+    method public default void onSetCarClimateStateSeatTemperatureLevel(int);
+    method public default void onSetCarClimateStateSeatVentilationLevel(int);
+    method public default void onSetCarClimateStateSteeringWheelHeat(int);
+    method public static String toString(int);
+    field public static final int OPERATION_STATUS_FEATURE_SETTING_NOT_ALLOWED = 4; // 0x4
+    field public static final int OPERATION_STATUS_FEATURE_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FEATURE_UNIMPLEMENTED = 1; // 0x1
+    field public static final int OPERATION_STATUS_FEATURE_UNSUPPORTED = 2; // 0x2
+    field public static final int OPERATION_STATUS_ILLEGAL_CAR_HARDWARE_STATE = 7; // 0x7
+    field public static final int OPERATION_STATUS_INSUFFICIENT_PERMISSION = 6; // 0x6
+    field public static final int OPERATION_STATUS_SUCCESS = 0; // 0x0
+    field public static final int OPERATION_STATUS_UNSUPPORTED_VALUE = 5; // 0x5
+    field public static final int OPERATION_STATUS_UPDATE_TIMEOUT = 8; // 0x8
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarUnit {
+    method public static String toString(int);
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int IMPERIAL_GALLON = 204; // 0xcc
+    field public static final int KILOMETER = 3; // 0x3
+    field public static final int KILOMETERS_PER_HOUR = 102; // 0x66
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int LITER = 202; // 0xca
+    field public static final int METER = 2; // 0x2
+    field public static final int METERS_PER_SEC = 101; // 0x65
+    field public static final int MILE = 4; // 0x4
+    field public static final int MILES_PER_HOUR = 103; // 0x67
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int MILLILITER = 201; // 0xc9
+    field public static final int MILLIMETER = 1; // 0x1
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int US_GALLON = 203; // 0xcb
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarValue<T> {
+    ctor public CarValue(T?, long, int);
+    ctor @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public CarValue(T?, long, int, java.util.List<androidx.car.app.hardware.common.CarZone!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public java.util.List<androidx.car.app.hardware.common.CarZone!> getCarZones();
+    method public int getStatus();
+    method public long getTimestampMillis();
+    method public T? getValue();
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_UNAVAILABLE = 3; // 0x3
+    field public static final int STATUS_UNIMPLEMENTED = 2; // 0x2
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(5) public final class CarZone {
+    method public int getColumn();
+    method public int getRow();
+    field public static final int CAR_ZONE_COLUMN_ALL = 16; // 0x10
+    field public static final int CAR_ZONE_COLUMN_CENTER = 48; // 0x30
+    field public static final int CAR_ZONE_COLUMN_DRIVER = 80; // 0x50
+    field public static final int CAR_ZONE_COLUMN_LEFT = 32; // 0x20
+    field public static final int CAR_ZONE_COLUMN_PASSENGER = 96; // 0x60
+    field public static final int CAR_ZONE_COLUMN_RIGHT = 64; // 0x40
+    field public static final androidx.car.app.hardware.common.CarZone CAR_ZONE_GLOBAL;
+    field public static final int CAR_ZONE_ROW_ALL = 0; // 0x0
+    field public static final int CAR_ZONE_ROW_EXCLUDE_FIRST = 4; // 0x4
+    field public static final int CAR_ZONE_ROW_FIRST = 1; // 0x1
+    field public static final int CAR_ZONE_ROW_SECOND = 2; // 0x2
+    field public static final int CAR_ZONE_ROW_THIRD = 3; // 0x3
+  }
+
+  public static final class CarZone.Builder {
+    ctor public CarZone.Builder();
+    method public androidx.car.app.hardware.common.CarZone build();
+    method public androidx.car.app.hardware.common.CarZone.Builder setColumn(int);
+    method public androidx.car.app.hardware.common.CarZone.Builder setRow(int);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public interface OnCarDataAvailableListener<T> {
+    method public void onCarDataAvailable(T);
+  }
+
+}
+
+package androidx.car.app.hardware.info {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Accelerometer {
+    ctor public Accelerometer(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getForces();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class CarHardwareLocation {
+    ctor public CarHardwareLocation(androidx.car.app.hardware.common.CarValue<android.location.Location!>);
+    method public androidx.car.app.hardware.common.CarValue<android.location.Location!> getLocation();
+  }
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarInfo {
+    method public void addEnergyLevelListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyLevel!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void addEvStatusListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EvStatus!>);
+    method public void addMileageListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Mileage!>);
+    method public void addSpeedListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Speed!>);
+    method public void addTollListener(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.TollCard!>);
+    method public void fetchEnergyProfile(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyProfile!>);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public default void fetchExteriorDimensions(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.ExteriorDimensions!>);
+    method public void fetchModel(java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Model!>);
+    method public void removeEnergyLevelListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EnergyLevel!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public void removeEvStatusListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.EvStatus!>);
+    method public void removeMileageListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Mileage!>);
+    method public void removeSpeedListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Speed!>);
+    method public void removeTollListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.TollCard!>);
+  }
+
+  @MainThread @androidx.car.app.annotations.RequiresCarApi(3) public interface CarSensors {
+    method public void addAccelerometerListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Accelerometer!>);
+    method public void addCarHardwareLocationListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.CarHardwareLocation!>);
+    method public void addCompassListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Compass!>);
+    method public void addGyroscopeListener(int, java.util.concurrent.Executor, androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Gyroscope!>);
+    method public void removeAccelerometerListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Accelerometer!>);
+    method public void removeCarHardwareLocationListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.CarHardwareLocation!>);
+    method public void removeCompassListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Compass!>);
+    method public void removeGyroscopeListener(androidx.car.app.hardware.common.OnCarDataAvailableListener<androidx.car.app.hardware.info.Gyroscope!>);
+    field public static final int UPDATE_RATE_FASTEST = 3; // 0x3
+    field public static final int UPDATE_RATE_NORMAL = 1; // 0x1
+    field public static final int UPDATE_RATE_UI = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Compass {
+    ctor public Compass(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getOrientations();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class EnergyLevel {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getBatteryPercent();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEnergyIsLow();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getFuelPercent();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getFuelVolumeDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemainingMeters();
+  }
+
+  public static final class EnergyLevel.Builder {
+    ctor public EnergyLevel.Builder();
+    method public androidx.car.app.hardware.info.EnergyLevel build();
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setBatteryPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setEnergyIsLow(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelVolumeDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemainingMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class EnergyProfile {
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!> getEvConnectorTypes();
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!> getFuelTypes();
+    field public static final int EVCONNECTOR_TYPE_CHADEMO = 3; // 0x3
+    field public static final int EVCONNECTOR_TYPE_COMBO_1 = 4; // 0x4
+    field public static final int EVCONNECTOR_TYPE_COMBO_2 = 5; // 0x5
+    field public static final int EVCONNECTOR_TYPE_GBT = 9; // 0x9
+    field public static final int EVCONNECTOR_TYPE_GBT_DC = 10; // 0xa
+    field public static final int EVCONNECTOR_TYPE_J1772 = 1; // 0x1
+    field public static final int EVCONNECTOR_TYPE_MENNEKES = 2; // 0x2
+    field public static final int EVCONNECTOR_TYPE_OTHER = 101; // 0x65
+    field public static final int EVCONNECTOR_TYPE_SCAME = 11; // 0xb
+    field public static final int EVCONNECTOR_TYPE_TESLA_HPWC = 7; // 0x7
+    field public static final int EVCONNECTOR_TYPE_TESLA_ROADSTER = 6; // 0x6
+    field public static final int EVCONNECTOR_TYPE_TESLA_SUPERCHARGER = 8; // 0x8
+    field public static final int EVCONNECTOR_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FUEL_TYPE_BIODIESEL = 5; // 0x5
+    field public static final int FUEL_TYPE_CNG = 8; // 0x8
+    field public static final int FUEL_TYPE_DIESEL_1 = 3; // 0x3
+    field public static final int FUEL_TYPE_DIESEL_2 = 4; // 0x4
+    field public static final int FUEL_TYPE_E85 = 6; // 0x6
+    field public static final int FUEL_TYPE_ELECTRIC = 10; // 0xa
+    field public static final int FUEL_TYPE_HYDROGEN = 11; // 0xb
+    field public static final int FUEL_TYPE_LEADED = 2; // 0x2
+    field public static final int FUEL_TYPE_LNG = 9; // 0x9
+    field public static final int FUEL_TYPE_LPG = 7; // 0x7
+    field public static final int FUEL_TYPE_OTHER = 12; // 0xc
+    field public static final int FUEL_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FUEL_TYPE_UNLEADED = 1; // 0x1
+  }
+
+  public static final class EnergyProfile.Builder {
+    ctor public EnergyProfile.Builder();
+    method public androidx.car.app.hardware.info.EnergyProfile build();
+    method public androidx.car.app.hardware.info.EnergyProfile.Builder setEvConnectorTypes(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!>);
+    method public androidx.car.app.hardware.info.EnergyProfile.Builder setFuelTypes(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Integer!>!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class EvStatus {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEvChargePortConnected();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEvChargePortOpen();
+  }
+
+  public static final class EvStatus.Builder {
+    ctor public EvStatus.Builder();
+    method public androidx.car.app.hardware.info.EvStatus build();
+    method public androidx.car.app.hardware.info.EvStatus.Builder setEvChargePortConnected(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+    method public androidx.car.app.hardware.info.EvStatus.Builder setEvChargePortOpen(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class ExteriorDimensions {
+    ctor public ExteriorDimensions();
+    ctor public ExteriorDimensions(androidx.car.app.hardware.common.CarValue<java.lang.Integer![]!>);
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer![]!> getExteriorDimensions();
+    field public static final int CURB_TO_CURB_TURNING_RADIUS_INDEX = 7; // 0x7
+    field public static final int HEIGHT_INDEX = 0; // 0x0
+    field public static final int LENGTH_INDEX = 1; // 0x1
+    field public static final int TRACK_WIDTH_FRONT_INDEX = 5; // 0x5
+    field public static final int TRACK_WIDTH_REAR_INDEX = 6; // 0x6
+    field public static final int WHEEL_BASE_INDEX = 4; // 0x4
+    field public static final int WIDTH_INCLUDING_MIRRORS_INDEX = 3; // 0x3
+    field public static final int WIDTH_INDEX = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Gyroscope {
+    ctor public Gyroscope(androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!>);
+    method public androidx.car.app.hardware.common.CarValue<java.util.List<java.lang.Float!>!> getRotations();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Mileage {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometerMeters();
+  }
+
+  public static final class Mileage.Builder {
+    ctor public Mileage.Builder();
+    method public androidx.car.app.hardware.info.Mileage build();
+    method public androidx.car.app.hardware.info.Mileage.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+    method public androidx.car.app.hardware.info.Mileage.Builder setOdometerMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Model {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.String!> getManufacturer();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.String!> getName();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getYear();
+  }
+
+  public static final class Model.Builder {
+    ctor public Model.Builder();
+    method public androidx.car.app.hardware.info.Model build();
+    method public androidx.car.app.hardware.info.Model.Builder setManufacturer(androidx.car.app.hardware.common.CarValue<java.lang.String!>);
+    method public androidx.car.app.hardware.info.Model.Builder setName(androidx.car.app.hardware.common.CarValue<java.lang.String!>);
+    method public androidx.car.app.hardware.info.Model.Builder setYear(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Speed {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeedMetersPerSecond();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeedMetersPerSecond();
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getSpeedDisplayUnit();
+  }
+
+  public static final class Speed.Builder {
+    ctor public Speed.Builder();
+    method public androidx.car.app.hardware.info.Speed build();
+    method public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.Speed.Builder setRawSpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
+    method public androidx.car.app.hardware.info.Speed.Builder setSpeedDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class TollCard {
+    method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getCardState();
+    field public static final int TOLLCARD_STATE_INVALID = 2; // 0x2
+    field public static final int TOLLCARD_STATE_NOT_INSERTED = 3; // 0x3
+    field public static final int TOLLCARD_STATE_UNKNOWN = 0; // 0x0
+    field public static final int TOLLCARD_STATE_VALID = 1; // 0x1
+  }
+
+  public static final class TollCard.Builder {
+    ctor public TollCard.Builder();
+    method public androidx.car.app.hardware.info.TollCard build();
+    method public androidx.car.app.hardware.info.TollCard.Builder setCardState(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
+  }
+
+}
+
+package androidx.car.app.managers {
+
+  public interface Manager {
+  }
+
+}
+
+package androidx.car.app.media {
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public interface CarAudioCallback {
+    method public void onStopRecording();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public class CarAudioCallbackDelegate {
+    method public void onStopRecording();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public abstract class CarAudioRecord {
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.car.app.media.CarAudioRecord create(androidx.car.app.CarContext);
+    method public int read(byte[], int, int);
+    method public void startRecording();
+    method public void stopRecording();
+    field public static final int AUDIO_CONTENT_BUFFER_SIZE = 512; // 0x200
+    field public static final String AUDIO_CONTENT_MIME = "audio/l16";
+    field public static final int AUDIO_CONTENT_SAMPLING_RATE = 16000; // 0x3e80
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class MediaPlaybackManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void registerMediaPlaybackToken(android.support.v4.media.session.MediaSessionCompat.Token);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class OpenMicrophoneRequest {
+    method public androidx.car.app.media.CarAudioCallbackDelegate getCarAudioCallbackDelegate();
+  }
+
+  public static final class OpenMicrophoneRequest.Builder {
+    ctor public OpenMicrophoneRequest.Builder(androidx.car.app.media.CarAudioCallback);
+    method public androidx.car.app.media.OpenMicrophoneRequest build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class OpenMicrophoneResponse {
+    method public androidx.car.app.media.CarAudioCallbackDelegate getCarAudioCallback();
+    method public java.io.InputStream getCarMicrophoneInputStream();
+  }
+
+  public static final class OpenMicrophoneResponse.Builder {
+    ctor public OpenMicrophoneResponse.Builder(androidx.car.app.media.CarAudioCallback);
+    method public androidx.car.app.media.OpenMicrophoneResponse build();
+    method public androidx.car.app.media.OpenMicrophoneResponse.Builder setCarMicrophoneDescriptor(android.os.ParcelFileDescriptor);
+  }
+
+}
+
+package androidx.car.app.media.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(8) public class MediaPlaybackTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.Header? getHeader();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class MediaPlaybackTemplate.Builder {
+    ctor public MediaPlaybackTemplate.Builder();
+    ctor public MediaPlaybackTemplate.Builder(androidx.car.app.media.model.MediaPlaybackTemplate);
+    method public androidx.car.app.media.model.MediaPlaybackTemplate build();
+    method public androidx.car.app.media.model.MediaPlaybackTemplate.Builder setHeader(androidx.car.app.model.Header?);
+  }
+
+}
+
+package androidx.car.app.mediaextensions {
+
+  public final class MediaBrowserExtras {
+    field public static final String KEY_HINT_VIEW_MAX_CATEGORY_GRID_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_CATEGORY_GRID_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_CATEGORY_LIST_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_CATEGORY_LIST_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_GRID_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_GRID_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_HINT_VIEW_MAX_ITEMS_WHILE_RESTRICTED = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_ITEMS_WHILE_RESTRICTED";
+    field public static final String KEY_HINT_VIEW_MAX_LIST_ITEMS_COUNT_PER_ROW = "androidx.car.app.mediaextensions.KEY_HINT_VIEW_MAX_LIST_ITEMS_COUNT_PER_ROW";
+    field public static final String KEY_ROOT_HINT_MAX_QUEUE_ITEMS_WHILE_RESTRICTED = "androidx.car.app.mediaextensions.KEY_ROOT_HINT_MAX_QUEUE_ITEMS_WHILE_RESTRICTED";
+    field public static final String KEY_ROOT_HINT_MEDIA_SESSION_API = "androidx.car.app.mediaextensions.KEY_ROOT_HINT_MEDIA_SESSION_API";
+  }
+
+  public final class MetadataExtras {
+    field public static final String KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI = "androidx.car.app.mediaextensions.KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI";
+    field public static final String KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI = "androidx.car.app.mediaextensions.KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI";
+    field public static final String KEY_DESCRIPTION_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+    field public static final String KEY_IMMERSIVE_AUDIO = "androidx.car.app.mediaextensions.KEY_IMMERSIVE_AUDIO";
+    field public static final String KEY_SUBTITLE_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics {
+
+  public class Constants {
+    field public static final String ACTION_ANALYTICS = "androidx.car.app.mediaextensions.analytics.action.ANALYTICS";
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics.client {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface AnalyticsCallback {
+    method public void onBrowseNodeChangeEvent(androidx.car.app.mediaextensions.analytics.event.BrowseChangeEvent);
+    method public void onErrorEvent(androidx.car.app.mediaextensions.analytics.event.ErrorEvent);
+    method public void onMediaClickedEvent(androidx.car.app.mediaextensions.analytics.event.MediaClickedEvent);
+    method public default void onUnknownEvent(android.os.Bundle);
+    method public void onViewChangeEvent(androidx.car.app.mediaextensions.analytics.event.ViewChangeEvent);
+    method public void onVisibleItemsEvent(androidx.car.app.mediaextensions.analytics.event.VisibleItemsEvent);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class RootHintsPopulator {
+    ctor public RootHintsPopulator(android.os.Bundle);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setAnalyticsOptIn(boolean);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setShareOem(boolean);
+    method public androidx.car.app.mediaextensions.analytics.client.RootHintsPopulator setSharePlatform(boolean);
+  }
+
+}
+
+package androidx.car.app.mediaextensions.analytics.event {
+
+  public abstract class AnalyticsEvent {
+    method public int getAnalyticsVersion();
+    method public String getComponent();
+    method public int getEventType();
+    method public long getTimestampMillis();
+    field public static final int EVENT_TYPE_BROWSE_NODE_CHANGED_EVENT = 3; // 0x3
+    field public static final int EVENT_TYPE_ERROR_EVENT = 5; // 0x5
+    field public static final int EVENT_TYPE_MEDIA_CLICKED_EVENT = 2; // 0x2
+    field public static final int EVENT_TYPE_UNKNOWN_EVENT = 0; // 0x0
+    field public static final int EVENT_TYPE_VIEW_CHANGE_EVENT = 4; // 0x4
+    field public static final int EVENT_TYPE_VISIBLE_ITEMS_EVENT = 1; // 0x1
+    field public static final int VIEW_ACTION_HIDE = 0; // 0x0
+    field public static final int VIEW_ACTION_MODE_NONE = 0; // 0x0
+    field public static final int VIEW_ACTION_MODE_SCROLL = 1; // 0x1
+    field public static final int VIEW_ACTION_SHOW = 1; // 0x1
+    field public static final int VIEW_COMPONENT_BROWSE_ACTION_OVERFLOW = 8; // 0x8
+    field public static final int VIEW_COMPONENT_BROWSE_LIST = 1; // 0x1
+    field public static final int VIEW_COMPONENT_BROWSE_TABS = 2; // 0x2
+    field public static final int VIEW_COMPONENT_ERROR_MESSAGE = 10; // 0xa
+    field public static final int VIEW_COMPONENT_LAUNCHER = 6; // 0x6
+    field public static final int VIEW_COMPONENT_MEDIA_HOST = 9; // 0x9
+    field public static final int VIEW_COMPONENT_MINI_PLAYBACK = 5; // 0x5
+    field public static final int VIEW_COMPONENT_PLAYBACK = 4; // 0x4
+    field public static final int VIEW_COMPONENT_QUEUE_LIST = 3; // 0x3
+    field public static final int VIEW_COMPONENT_SETTINGS_VIEW = 7; // 0x7
+    field public static final int VIEW_COMPONENT_UNKNOWN_COMPONENT = 0; // 0x0
+  }
+
+  public class BrowseChangeEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getBrowseMode();
+    method public String getBrowseNodeId();
+    method public int getViewAction();
+    field public static final int BROWSE_MODE_LINK = 4; // 0x4
+    field public static final int BROWSE_MODE_LINK_BROWSE = 5; // 0x5
+    field public static final int BROWSE_MODE_SEARCH_BROWSE = 6; // 0x6
+    field public static final int BROWSE_MODE_SEARCH_RESULTS = 7; // 0x7
+    field public static final int BROWSE_MODE_TREE_BROWSE = 2; // 0x2
+    field public static final int BROWSE_MODE_TREE_ROOT = 1; // 0x1
+    field public static final int BROWSE_MODE_TREE_TAB = 3; // 0x3
+    field public static final int BROWSE_MODE_UNKNOWN = 0; // 0x0
+  }
+
+  public class ErrorEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getErrorCode();
+    field public static final int ERROR_CODE_INVALID_BUNDLE = 1; // 0x1
+    field public static final int ERROR_CODE_INVALID_EVENT = 2; // 0x2
+    field public static final int ERROR_CODE_INVALID_EXTRAS = 0; // 0x0
+  }
+
+  public class MediaClickedEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public String? getMediaId();
+    method public int getViewComponent();
+  }
+
+  public class ViewChangeEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public int getViewAction();
+    method public int getViewComponent();
+  }
+
+  public class VisibleItemsEvent extends androidx.car.app.mediaextensions.analytics.event.AnalyticsEvent {
+    method public java.util.List<java.lang.String!>? getItemsIds();
+    method public String? getNodeId();
+    method public int getViewAction();
+    method public int getViewActionMode();
+    method public int getViewComponent();
+  }
+
+}
+
+package androidx.car.app.messaging {
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public class MessagingServiceConstants {
+    field public static final String ACTION_HANDLE_CAR_MESSAGING = "androidx.car.app.messaging.action.HANDLE_CAR_MESSAGING";
+  }
+
+}
+
+package androidx.car.app.messaging.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class CarMessage {
+    method public androidx.car.app.model.CarText? getBody();
+    method public String? getMultimediaMimeType();
+    method public android.net.Uri? getMultimediaUri();
+    method public long getReceivedTimeEpochMillis();
+    method public androidx.core.app.Person? getSender();
+    method public boolean isRead();
+  }
+
+  public static final class CarMessage.Builder {
+    ctor public CarMessage.Builder();
+    method public androidx.car.app.messaging.model.CarMessage build();
+    method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaMimeType(String?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaUri(android.net.Uri?);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person?);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface ConversationCallback {
+    method public void onMarkAsRead();
+    method public void onTextReply(String);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public interface ConversationCallbackDelegate {
+    method public void sendMarkAsRead(androidx.car.app.OnDoneCallback);
+    method public void sendTextReply(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public class ConversationItem implements androidx.car.app.model.Item {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.messaging.model.ConversationCallbackDelegate getConversationCallbackDelegate();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getId();
+    method public java.util.List<androidx.car.app.messaging.model.CarMessage!> getMessages();
+    method public androidx.core.app.Person getSelf();
+    method public androidx.car.app.model.CarText getTitle();
+    method public boolean isGroupConversation();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+  }
+
+  public static final class ConversationItem.Builder {
+    ctor public ConversationItem.Builder();
+    ctor public ConversationItem.Builder(androidx.car.app.messaging.model.ConversationItem);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.messaging.model.ConversationItem build();
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setConversationCallback(androidx.car.app.messaging.model.ConversationCallback);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setId(String);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.messaging.model.ConversationItem.Builder setIndexable(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setMessages(java.util.List<androidx.car.app.messaging.model.CarMessage!>);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setSelf(androidx.core.app.Person);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setTitle(androidx.car.app.model.CarText);
+  }
+
+}
+
+package androidx.car.app.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Action {
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public int getFlags();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public int getType();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+    method public boolean isStandard();
+    method public static String typeToString(int);
+    field public static final androidx.car.app.model.Action APP_ICON;
+    field public static final androidx.car.app.model.Action BACK;
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final androidx.car.app.model.Action COMPOSE_MESSAGE;
+    field @androidx.car.app.annotations.RequiresCarApi(5) public static final int FLAG_DEFAULT = 4; // 0x4
+    field @androidx.car.app.annotations.RequiresCarApi(5) public static final int FLAG_IS_PERSISTENT = 2; // 0x2
+    field @androidx.car.app.annotations.RequiresCarApi(4) public static final int FLAG_PRIMARY = 1; // 0x1
+    field public static final androidx.car.app.model.Action PAN;
+    field public static final int TYPE_APP_ICON = 65538; // 0x10002
+    field public static final int TYPE_BACK = 65539; // 0x10003
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int TYPE_COMPOSE_MESSAGE = 65541; // 0x10005
+    field public static final int TYPE_CUSTOM = 1; // 0x1
+    field public static final int TYPE_PAN = 65540; // 0x10004
+  }
+
+  public static final class Action.Builder {
+    ctor public Action.Builder();
+    ctor @androidx.car.app.annotations.RequiresCarApi(2) public Action.Builder(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Action build();
+    method public androidx.car.app.model.Action.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Action.Builder setEnabled(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.Action.Builder setFlags(int);
+    method public androidx.car.app.model.Action.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Action.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.Action.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Action.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ActionStrip {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Action? getFirstActionOfType(int);
+  }
+
+  public static final class ActionStrip.Builder {
+    ctor public ActionStrip.Builder();
+    method public androidx.car.app.model.ActionStrip.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ActionStrip build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Alert {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.AlertCallbackDelegate? getCallbackDelegate();
+    method public long getDurationMillis();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getId();
+    method public androidx.car.app.model.CarText? getSubtitle();
+    method public androidx.car.app.model.CarText getTitle();
+    field public static final int DURATION_SHOW_INDEFINITELY = 2147483647; // 0x7fffffff
+  }
+
+  public static final class Alert.Builder {
+    ctor public Alert.Builder(int, androidx.car.app.model.CarText, long);
+    method public androidx.car.app.model.Alert.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Alert build();
+    method public androidx.car.app.model.Alert.Builder setCallback(androidx.car.app.model.AlertCallback);
+    method public androidx.car.app.model.Alert.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Alert.Builder setSubtitle(androidx.car.app.model.CarText);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface AlertCallback {
+    method public void onCancel(int);
+    method public void onDismiss();
+    field public static final int REASON_NOT_SUPPORTED = 3; // 0x3
+    field public static final int REASON_TIMEOUT = 1; // 0x1
+    field public static final int REASON_USER_ACTION = 2; // 0x2
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface AlertCallbackDelegate {
+    method public void sendCancel(int, androidx.car.app.OnDoneCallback);
+    method public void sendDismiss(androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class Badge {
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public boolean hasDot();
+  }
+
+  public static final class Badge.Builder {
+    ctor public Badge.Builder();
+    method public androidx.car.app.model.Badge build();
+    method public androidx.car.app.model.Badge.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.Badge.Builder setHasDot(boolean);
+    method public androidx.car.app.model.Badge.Builder setIcon(androidx.car.app.model.CarIcon);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarColor {
+    method public static androidx.car.app.model.CarColor createCustom(@ColorInt int, @ColorInt int);
+    method @ColorInt public int getColor();
+    method @ColorInt public int getColorDark();
+    method public int getType();
+    field public static final androidx.car.app.model.CarColor BLUE;
+    field public static final androidx.car.app.model.CarColor DEFAULT;
+    field public static final androidx.car.app.model.CarColor GREEN;
+    field public static final androidx.car.app.model.CarColor PRIMARY;
+    field public static final androidx.car.app.model.CarColor RED;
+    field public static final androidx.car.app.model.CarColor SECONDARY;
+    field public static final int TYPE_BLUE = 6; // 0x6
+    field public static final int TYPE_CUSTOM = 0; // 0x0
+    field public static final int TYPE_DEFAULT = 1; // 0x1
+    field public static final int TYPE_GREEN = 5; // 0x5
+    field public static final int TYPE_PRIMARY = 2; // 0x2
+    field public static final int TYPE_RED = 4; // 0x4
+    field public static final int TYPE_SECONDARY = 3; // 0x3
+    field public static final int TYPE_YELLOW = 7; // 0x7
+    field public static final androidx.car.app.model.CarColor YELLOW;
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarIcon {
+    method public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method public androidx.car.app.model.CarColor? getTint();
+    method public int getType();
+    field public static final androidx.car.app.model.CarIcon ALERT;
+    field public static final androidx.car.app.model.CarIcon APP_ICON;
+    field public static final androidx.car.app.model.CarIcon BACK;
+    field @androidx.car.app.annotations.RequiresCarApi(7) public static final androidx.car.app.model.CarIcon COMPOSE_MESSAGE;
+    field public static final androidx.car.app.model.CarIcon ERROR;
+    field @androidx.car.app.annotations.RequiresCarApi(2) public static final androidx.car.app.model.CarIcon PAN;
+    field public static final int TYPE_ALERT = 4; // 0x4
+    field public static final int TYPE_APP_ICON = 5; // 0x5
+    field public static final int TYPE_BACK = 3; // 0x3
+    field public static final int TYPE_COMPOSE_MESSAGE = 8; // 0x8
+    field public static final int TYPE_CUSTOM = 1; // 0x1
+    field public static final int TYPE_ERROR = 6; // 0x6
+    field public static final int TYPE_PAN = 7; // 0x7
+  }
+
+  public static final class CarIcon.Builder {
+    ctor public CarIcon.Builder(androidx.car.app.model.CarIcon);
+    ctor public CarIcon.Builder(androidx.core.graphics.drawable.IconCompat);
+    method public androidx.car.app.model.CarIcon build();
+    method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarIconSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon);
+    method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon, int);
+    method public int getAlignment();
+    method public androidx.car.app.model.CarIcon getIcon();
+    field public static final int ALIGN_BASELINE = 1; // 0x1
+    field public static final int ALIGN_BOTTOM = 0; // 0x0
+    field public static final int ALIGN_CENTER = 2; // 0x2
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarLocation {
+    method public static androidx.car.app.model.CarLocation create(android.location.Location);
+    method public static androidx.car.app.model.CarLocation create(double, double);
+    method public double getLatitude();
+    method public double getLongitude();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public class CarSpan extends android.text.style.CharacterStyle {
+    ctor public CarSpan();
+    method public void updateDrawState(android.text.TextPaint);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class CarText {
+    method public static androidx.car.app.model.CarText create(CharSequence);
+    method public java.util.List<java.lang.CharSequence!> getVariants();
+    method public boolean isEmpty();
+    method public static boolean isNullOrEmpty(androidx.car.app.model.CarText?);
+    method public CharSequence toCharSequence();
+  }
+
+  @SuppressCompatibility public static final class CarText.Builder {
+    ctor public CarText.Builder(CharSequence);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.CarText.Builder addVariant(CharSequence);
+    method public androidx.car.app.model.CarText build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(2) public final class ClickableSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.ClickableSpan create(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.OnClickDelegate getOnClickDelegate();
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public interface Content {
+    method public String getContentId();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DateTimeWithZone {
+    method @RequiresApi(26) public static androidx.car.app.model.DateTimeWithZone create(java.time.ZonedDateTime);
+    method public static androidx.car.app.model.DateTimeWithZone create(long, @IntRange(from=0xffff02e0, to=64800) int, String);
+    method public static androidx.car.app.model.DateTimeWithZone create(long, java.util.TimeZone);
+    method public long getTimeSinceEpochMillis();
+    method public int getZoneOffsetSeconds();
+    method public String? getZoneShortName();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Distance {
+    method public static androidx.car.app.model.Distance create(double, int);
+    method public double getDisplayDistance();
+    method public int getDisplayUnit();
+    field public static final int UNIT_FEET = 6; // 0x6
+    field public static final int UNIT_KILOMETERS = 2; // 0x2
+    field public static final int UNIT_KILOMETERS_P1 = 3; // 0x3
+    field public static final int UNIT_METERS = 1; // 0x1
+    field public static final int UNIT_MILES = 4; // 0x4
+    field public static final int UNIT_MILES_P1 = 5; // 0x5
+    field public static final int UNIT_YARDS = 7; // 0x7
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DistanceSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.DistanceSpan create(androidx.car.app.model.Distance);
+    method public androidx.car.app.model.Distance getDistance();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class DurationSpan extends androidx.car.app.model.CarSpan {
+    method @RequiresApi(26) public static androidx.car.app.model.DurationSpan create(java.time.Duration);
+    method public static androidx.car.app.model.DurationSpan create(long);
+    method public long getDurationSeconds();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
+    method public static androidx.car.app.model.ForegroundCarColorSpan create(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.CarColor getColor();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridItem implements androidx.car.app.model.Item {
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Badge? getBadge();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public int getImageType();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public androidx.car.app.model.CarText? getText();
+    method public androidx.car.app.model.CarText? getTitle();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+    method public boolean isLoading();
+    field public static final int IMAGE_TYPE_ICON = 1; // 0x1
+    field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
+  }
+
+  public static final class GridItem.Builder {
+    ctor public GridItem.Builder();
+    method public androidx.car.app.model.GridItem build();
+    method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
+    method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setIndexable(boolean);
+    method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
+    method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.GridItem.Builder setText(CharSequence);
+    method public androidx.car.app.model.GridItem.Builder setTitle(androidx.car.app.model.CarText?);
+    method public androidx.car.app.model.GridItem.Builder setTitle(CharSequence?);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class GridSection extends androidx.car.app.model.Section<androidx.car.app.model.GridItem!> {
+    method public int getItemImageShape();
+    method public int getItemSize();
+    field public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+    field public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+    field public static final int ITEM_SIZE_LARGE = 3; // 0x3
+    field public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+    field public static final int ITEM_SIZE_SMALL = 1; // 0x1
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
+    ctor public GridSection.Builder();
+    method public androidx.car.app.model.GridSection build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemImageShape();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemSize();
+    method public androidx.car.app.model.ItemList? getSingleList();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_LARGE = 4; // 0x4
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_SMALL = 1; // 0x1
+  }
+
+  public static final class GridTemplate.Builder {
+    ctor public GridTemplate.Builder();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.GridTemplate build();
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
+    method public androidx.car.app.model.GridTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.GridTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Header {
+    method public java.util.List<androidx.car.app.model.Action!> getEndHeaderActions();
+    method public androidx.car.app.model.Action? getStartHeaderAction();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class Header.Builder {
+    ctor public Header.Builder();
+    method public androidx.car.app.model.Header.Builder addEndHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Header build();
+    method public androidx.car.app.model.Header.Builder setStartHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Header.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Header.Builder setTitle(CharSequence);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public interface InputCallback {
+    method public default void onInputSubmitted(String);
+    method public default void onInputTextChanged(String);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public interface InputCallbackDelegate {
+    method public void sendInputSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void sendInputTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface Item {
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ItemList {
+    method public java.util.List<androidx.car.app.model.Item!> getItems();
+    method public androidx.car.app.model.CarText? getNoItemsMessage();
+    method public androidx.car.app.model.OnItemVisibilityChangedDelegate? getOnItemVisibilityChangedDelegate();
+    method public androidx.car.app.model.OnSelectedDelegate? getOnSelectedDelegate();
+    method public int getSelectedIndex();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ItemList.Builder toBuilder();
+  }
+
+  public static final class ItemList.Builder {
+    ctor public ItemList.Builder();
+    method public androidx.car.app.model.ItemList.Builder addItem(androidx.car.app.model.Item);
+    method public androidx.car.app.model.ItemList build();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ItemList.Builder clearItems();
+    method public androidx.car.app.model.ItemList.Builder setNoItemsMessage(CharSequence);
+    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangedListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener);
+    method public androidx.car.app.model.ItemList.Builder setOnSelectedListener(androidx.car.app.model.ItemList.OnSelectedListener);
+    method public androidx.car.app.model.ItemList.Builder setSelectedIndex(@IntRange(from=0) int);
+  }
+
+  public static interface ItemList.OnItemVisibilityChangedListener {
+    method public void onItemVisibilityChanged(int, int);
+  }
+
+  public static interface ItemList.OnSelectedListener {
+    method public void onSelected(int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ListTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public java.util.List<androidx.car.app.model.SectionedItemList!> getSectionedLists();
+    method public androidx.car.app.model.ItemList? getSingleList();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder toBuilder();
+  }
+
+  public static final class ListTemplate.Builder {
+    ctor public ListTemplate.Builder();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.ListTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
+    method public androidx.car.app.model.ListTemplate build();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder clearSectionedLists();
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.ListTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.ListTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class LongMessageTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText getMessage();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public static final class LongMessageTemplate.Builder {
+    ctor public LongMessageTemplate.Builder(CharSequence);
+    method public androidx.car.app.model.LongMessageTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.LongMessageTemplate build();
+    method public androidx.car.app.model.LongMessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.LongMessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.LongMessageTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getDebugMessage();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public androidx.car.app.model.CarText getMessage();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public boolean isLoading();
+  }
+
+  public static final class MessageTemplate.Builder {
+    ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
+    ctor public MessageTemplate.Builder(CharSequence);
+    method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.MessageTemplate build();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
+    method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.MessageTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.MessageTemplate.Builder setIcon(androidx.car.app.model.CarIcon);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setLoading(boolean);
+    method @Deprecated public androidx.car.app.model.MessageTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Metadata {
+    method public androidx.car.app.model.Place? getPlace();
+    field public static final androidx.car.app.model.Metadata EMPTY_METADATA;
+  }
+
+  public static final class Metadata.Builder {
+    ctor public Metadata.Builder();
+    ctor public Metadata.Builder(androidx.car.app.model.Metadata);
+    method public androidx.car.app.model.Metadata build();
+    method public androidx.car.app.model.Metadata.Builder setPlace(androidx.car.app.model.Place);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnCheckedChangeDelegate {
+    method public void sendCheckedChange(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnClickDelegate {
+    method public boolean isParkedOnly();
+    method public void sendClick(androidx.car.app.OnDoneCallback);
+  }
+
+  public interface OnClickListener {
+    method public void onClick();
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public interface OnContentRefreshDelegate {
+    method public void sendContentRefreshRequested(androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public interface OnContentRefreshListener {
+    method public void onContentRefreshRequested();
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnItemVisibilityChangedDelegate {
+    method public void sendItemVisibilityChanged(int, int, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface OnSelectedDelegate {
+    method public void sendSelected(int, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Pane {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.CarIcon? getImage();
+    method public java.util.List<androidx.car.app.model.Row!> getRows();
+    method public boolean isLoading();
+  }
+
+  public static final class Pane.Builder {
+    ctor public Pane.Builder();
+    method public androidx.car.app.model.Pane.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Pane.Builder addRow(androidx.car.app.model.Row);
+    method public androidx.car.app.model.Pane build();
+    method @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.Pane.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Pane.Builder setLoading(boolean);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PaneTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.Pane getPane();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class PaneTemplate.Builder {
+    ctor public PaneTemplate.Builder(androidx.car.app.model.Pane);
+    method public androidx.car.app.model.PaneTemplate build();
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.PaneTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.model.PaneTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ParkedOnlyOnClickListener implements androidx.car.app.model.OnClickListener {
+    method public static androidx.car.app.model.ParkedOnlyOnClickListener create(androidx.car.app.model.OnClickListener);
+    method public void onClick();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Place {
+    method public androidx.car.app.model.CarLocation getLocation();
+    method public androidx.car.app.model.PlaceMarker? getMarker();
+  }
+
+  public static final class Place.Builder {
+    ctor public Place.Builder(androidx.car.app.model.CarLocation);
+    ctor public Place.Builder(androidx.car.app.model.Place);
+    method public androidx.car.app.model.Place build();
+    method public androidx.car.app.model.Place.Builder setMarker(androidx.car.app.model.PlaceMarker);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceListMapTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Place? getAnchor();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.ItemList? getItemList();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.OnContentRefreshDelegate? getOnContentRefreshDelegate();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public boolean isCurrentLocationEnabled();
+    method public boolean isLoading();
+  }
+
+  public static final class PlaceListMapTemplate.Builder {
+    ctor public PlaceListMapTemplate.Builder();
+    method public androidx.car.app.model.PlaceListMapTemplate build();
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setAnchor(androidx.car.app.model.Place);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setCurrentLocationEnabled(boolean);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setLoading(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.PlaceListMapTemplate.Builder setOnContentRefreshListener(androidx.car.app.model.OnContentRefreshListener);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceMarker {
+    method public androidx.car.app.model.CarColor? getColor();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getIconType();
+    method public androidx.car.app.model.CarText? getLabel();
+    field public static final int TYPE_ICON = 0; // 0x0
+    field public static final int TYPE_IMAGE = 1; // 0x1
+  }
+
+  public static final class PlaceMarker.Builder {
+    ctor public PlaceMarker.Builder();
+    method public androidx.car.app.model.PlaceMarker build();
+    method public androidx.car.app.model.PlaceMarker.Builder setColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.model.PlaceMarker.Builder setIcon(androidx.car.app.model.CarIcon, int);
+    method public androidx.car.app.model.PlaceMarker.Builder setLabel(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Row implements androidx.car.app.model.Item {
+    method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.Metadata? getMetadata();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public int getNumericDecoration();
+    method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
+    method public int getRowImageType();
+    method public java.util.List<androidx.car.app.model.CarText!> getTexts();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public androidx.car.app.model.Toggle? getToggle();
+    method public boolean isBrowsable();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public boolean isIndexable();
+    method public androidx.car.app.model.Row row();
+    method public CharSequence yourBoat();
+    field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int IMAGE_TYPE_EXTRA_SMALL = 8; // 0x8
+    field public static final int IMAGE_TYPE_ICON = 4; // 0x4
+    field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
+    field public static final int IMAGE_TYPE_SMALL = 1; // 0x1
+    field public static final int NO_DECORATION = -1; // 0xffffffff
+  }
+
+  public static final class Row.Builder {
+    ctor public Row.Builder();
+    method @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.Row.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Row.Builder addText(CharSequence);
+    method public androidx.car.app.model.Row build();
+    method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Row.Builder setEnabled(boolean);
+    method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon, int);
+    method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder setIndexable(boolean);
+    method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
+    method @IntRange(from=0) @androidx.car.app.annotations.RequiresCarApi(6) public androidx.car.app.model.Row.Builder setNumericDecoration(int);
+    method public androidx.car.app.model.Row.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
+    method public androidx.car.app.model.Row.Builder setTitle(androidx.car.app.model.CarText);
+    method public androidx.car.app.model.Row.Builder setTitle(CharSequence);
+    method public androidx.car.app.model.Row.Builder setToggle(androidx.car.app.model.Toggle);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class RowSection extends androidx.car.app.model.Section<androidx.car.app.model.Row!> {
+    method public int getInitialSelectedIndex();
+    method public boolean isSelectionGroup();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
+    ctor public RowSection.Builder();
+    method public androidx.car.app.model.RowSection build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface SearchCallbackDelegate {
+    method public void sendSearchSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void sendSearchTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SearchTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public String? getInitialSearchText();
+    method public androidx.car.app.model.ItemList? getItemList();
+    method public androidx.car.app.model.SearchCallbackDelegate getSearchCallbackDelegate();
+    method public String? getSearchHint();
+    method public boolean isLoading();
+    method public boolean isShowKeyboardByDefault();
+  }
+
+  public static final class SearchTemplate.Builder {
+    ctor public SearchTemplate.Builder(androidx.car.app.model.SearchTemplate.SearchCallback);
+    method public androidx.car.app.model.SearchTemplate build();
+    method public androidx.car.app.model.SearchTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.SearchTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.SearchTemplate.Builder setInitialSearchText(String);
+    method public androidx.car.app.model.SearchTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method public androidx.car.app.model.SearchTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.SearchTemplate.Builder setSearchHint(String);
+    method public androidx.car.app.model.SearchTemplate.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  public static interface SearchTemplate.SearchCallback {
+    method public default void onSearchSubmitted(String);
+    method public default void onSearchTextChanged(String);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
+    ctor protected Section();
+    ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
+    method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
+    method public androidx.car.app.model.CarText? getNoItemsMessage();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
+    ctor protected Section.BaseBuilder();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setNoItemsMessage(androidx.car.app.model.CarText?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setNoItemsMessage(CharSequence?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setTitle(androidx.car.app.model.CarText?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setTitle(CharSequence?);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class SectionedItemList {
+    method public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, CharSequence);
+    method public androidx.car.app.model.CarText getHeader();
+    method public androidx.car.app.model.ItemList getItemList();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public final class SectionedItemTemplate implements androidx.car.app.model.Template {
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.Header? getHeader();
+    method public java.util.List<androidx.car.app.model.Section<? extends java.lang.Object!>!> getSections();
+    method public boolean isAlphabeticalIndexingAllowed();
+    method public boolean isLoading();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class SectionedItemTemplate.Builder {
+    ctor public SectionedItemTemplate.Builder();
+    ctor public SectionedItemTemplate.Builder(androidx.car.app.model.SectionedItemTemplate);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder addAction(androidx.car.app.model.Action);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder addSection(androidx.car.app.model.Section<? extends java.lang.Object!>);
+    method public androidx.car.app.model.SectionedItemTemplate build();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder clearActions();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder clearSections();
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setActions(java.util.List<androidx.car.app.model.Action!>);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setAlphabeticalIndexingAllowed(boolean);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setHeader(androidx.car.app.model.Header?);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setLoading(boolean);
+    method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.SectionedItemTemplate.Builder setSections(java.util.List<androidx.car.app.model.Section<? extends java.lang.Object!>!>);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public final class Tab implements androidx.car.app.model.Content {
+    method public String getContentId();
+    method public androidx.car.app.model.CarIcon getIcon();
+    method public androidx.car.app.model.CarText getTitle();
+  }
+
+  public static final class Tab.Builder {
+    ctor public Tab.Builder();
+    ctor public Tab.Builder(androidx.car.app.model.Tab);
+    method public androidx.car.app.model.Tab build();
+    method public androidx.car.app.model.Tab.Builder setContentId(String);
+    method public androidx.car.app.model.Tab.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.model.Tab.Builder setTitle(CharSequence);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public interface TabCallbackDelegate {
+    method public void sendTabSelected(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class TabContents implements androidx.car.app.model.Content {
+    method public String getContentId();
+    method public androidx.car.app.model.Template getTemplate();
+    field public static final String CONTENT_ID = "TAB_CONTENTS_CONTENT_ID";
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class TabContents.Api8Builder {
+    ctor public TabContents.Api8Builder(androidx.car.app.model.Template);
+    method public androidx.car.app.model.TabContents build();
+  }
+
+  public static final class TabContents.Builder {
+    ctor public TabContents.Builder(androidx.car.app.model.Template);
+    method public androidx.car.app.model.TabContents build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(6) public class TabTemplate implements androidx.car.app.model.Template {
+    method public String getActiveTabContentId();
+    method public androidx.car.app.model.Action getHeaderAction();
+    method public androidx.car.app.model.TabCallbackDelegate getTabCallbackDelegate();
+    method public androidx.car.app.model.TabContents getTabContents();
+    method public java.util.List<androidx.car.app.model.Tab!> getTabs();
+    method public boolean isLoading();
+  }
+
+  public static final class TabTemplate.Builder {
+    ctor public TabTemplate.Builder(androidx.car.app.model.TabTemplate);
+    ctor public TabTemplate.Builder(androidx.car.app.model.TabTemplate.TabCallback);
+    method public androidx.car.app.model.TabTemplate.Builder addTab(androidx.car.app.model.Tab);
+    method public androidx.car.app.model.TabTemplate build();
+    method public androidx.car.app.model.TabTemplate.Builder setActiveTabContentId(String);
+    method public androidx.car.app.model.TabTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.TabTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.TabTemplate.Builder setTabContents(androidx.car.app.model.TabContents);
+  }
+
+  public static interface TabTemplate.TabCallback {
+    method public default void onTabSelected(String);
+  }
+
+  @androidx.car.app.annotations.CarProtocol public interface Template {
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TemplateInfo {
+    ctor public TemplateInfo(Class<? extends androidx.car.app.model.Template!>, String);
+    method public Class<? extends androidx.car.app.model.Template!> getTemplateClass();
+    method public String getTemplateId();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TemplateWrapper {
+    method public static androidx.car.app.model.TemplateWrapper copyOf(androidx.car.app.model.TemplateWrapper);
+    method public int getCurrentTaskStep();
+    method public String getId();
+    method public androidx.car.app.model.Template getTemplate();
+    method public java.util.List<androidx.car.app.model.TemplateInfo!> getTemplateInfosForScreenStack();
+    method public boolean isRefresh();
+    method public void setCurrentTaskStep(int);
+    method public void setId(String);
+    method public void setRefresh(boolean);
+    method public void setTemplate(androidx.car.app.model.Template);
+    method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template);
+    method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template, String);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Toggle {
+    method public androidx.car.app.model.OnCheckedChangeDelegate getOnCheckedChangeDelegate();
+    method public boolean isChecked();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public boolean isEnabled();
+  }
+
+  public static final class Toggle.Builder {
+    ctor public Toggle.Builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
+    method public androidx.car.app.model.Toggle build();
+    method public androidx.car.app.model.Toggle.Builder setChecked(boolean);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Toggle.Builder setEnabled(boolean);
+  }
+
+  public static interface Toggle.OnCheckedChangeListener {
+    method public void onCheckedChange(boolean);
+  }
+
+}
+
+package androidx.car.app.model.signin {
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class InputSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.CarText? getDefaultValue();
+    method public androidx.car.app.model.CarText? getErrorMessage();
+    method public androidx.car.app.model.CarText? getHint();
+    method public androidx.car.app.model.InputCallbackDelegate getInputCallbackDelegate();
+    method public int getInputType();
+    method public int getKeyboardType();
+    method public boolean isShowKeyboardByDefault();
+    field public static final int INPUT_TYPE_DEFAULT = 1; // 0x1
+    field public static final int INPUT_TYPE_PASSWORD = 2; // 0x2
+    field public static final int KEYBOARD_DEFAULT = 1; // 0x1
+    field public static final int KEYBOARD_EMAIL = 2; // 0x2
+    field public static final int KEYBOARD_NUMBER = 4; // 0x4
+    field public static final int KEYBOARD_PHONE = 3; // 0x3
+  }
+
+  public static final class InputSignInMethod.Builder {
+    ctor public InputSignInMethod.Builder(androidx.car.app.model.InputCallback);
+    method public androidx.car.app.model.signin.InputSignInMethod build();
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setDefaultValue(String);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setErrorMessage(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setHint(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setInputType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setKeyboardType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class PinSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public PinSignInMethod(CharSequence);
+    method public androidx.car.app.model.CarText getPinCode();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class ProviderSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public ProviderSignInMethod(androidx.car.app.model.Action);
+    method public androidx.car.app.model.Action getAction();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(4) public final class QRCodeSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    ctor public QRCodeSignInMethod(android.net.Uri);
+    method public android.net.Uri getUri();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class SignInTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getAdditionalText();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText? getInstructions();
+    method public androidx.car.app.model.signin.SignInTemplate.SignInMethod getSignInMethod();
+    method public androidx.car.app.model.CarText? getTitle();
+    method public boolean isLoading();
+  }
+
+  @androidx.car.app.annotations.RequiresCarApi(2) public static final class SignInTemplate.Builder {
+    ctor public SignInTemplate.Builder(androidx.car.app.model.signin.SignInTemplate.SignInMethod);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate build();
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setAdditionalText(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setInstructions(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setLoading(boolean);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setTitle(CharSequence);
+  }
+
+  public static interface SignInTemplate.SignInMethod {
+  }
+
+}
+
+package androidx.car.app.navigation {
+
+  public class NavigationManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void clearNavigationManagerCallback();
+    method @MainThread public void navigationEnded();
+    method @MainThread public void navigationStarted();
+    method @MainThread public void setNavigationManagerCallback(androidx.car.app.navigation.NavigationManagerCallback);
+    method @MainThread public void setNavigationManagerCallback(java.util.concurrent.Executor, androidx.car.app.navigation.NavigationManagerCallback);
+    method @MainThread public void updateTrip(androidx.car.app.navigation.model.Trip);
+  }
+
+  public interface NavigationManagerCallback {
+    method public default void onAutoDriveEnabled();
+    method public default void onStopNavigation();
+  }
+
+}
+
+package androidx.car.app.navigation.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Destination {
+    method public androidx.car.app.model.CarText? getAddress();
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.CarText? getName();
+  }
+
+  public static final class Destination.Builder {
+    ctor public Destination.Builder();
+    method public androidx.car.app.navigation.model.Destination build();
+    method public androidx.car.app.navigation.model.Destination.Builder setAddress(CharSequence);
+    method public androidx.car.app.navigation.model.Destination.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Destination.Builder setName(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Lane {
+    method public java.util.List<androidx.car.app.navigation.model.LaneDirection!> getDirections();
+  }
+
+  public static final class Lane.Builder {
+    ctor public Lane.Builder();
+    method public androidx.car.app.navigation.model.Lane.Builder addDirection(androidx.car.app.navigation.model.LaneDirection);
+    method public androidx.car.app.navigation.model.Lane build();
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class LaneDirection {
+    method public static androidx.car.app.navigation.model.LaneDirection create(int, boolean);
+    method public int getShape();
+    method public boolean isRecommended();
+    field public static final int SHAPE_NORMAL_LEFT = 5; // 0x5
+    field public static final int SHAPE_NORMAL_RIGHT = 6; // 0x6
+    field public static final int SHAPE_SHARP_LEFT = 7; // 0x7
+    field public static final int SHAPE_SHARP_RIGHT = 8; // 0x8
+    field public static final int SHAPE_SLIGHT_LEFT = 3; // 0x3
+    field public static final int SHAPE_SLIGHT_RIGHT = 4; // 0x4
+    field public static final int SHAPE_STRAIGHT = 2; // 0x2
+    field public static final int SHAPE_UNKNOWN = 1; // 0x1
+    field public static final int SHAPE_U_TURN_LEFT = 9; // 0x9
+    field public static final int SHAPE_U_TURN_RIGHT = 10; // 0xa
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Maneuver {
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public int getRoundaboutExitAngle();
+    method public int getRoundaboutExitNumber();
+    method public int getType();
+    field public static final int TYPE_DEPART = 1; // 0x1
+    field public static final int TYPE_DESTINATION = 39; // 0x27
+    field public static final int TYPE_DESTINATION_LEFT = 41; // 0x29
+    field public static final int TYPE_DESTINATION_RIGHT = 42; // 0x2a
+    field public static final int TYPE_DESTINATION_STRAIGHT = 40; // 0x28
+    field public static final int TYPE_FERRY_BOAT = 37; // 0x25
+    field public static final int TYPE_FERRY_BOAT_LEFT = 47; // 0x2f
+    field public static final int TYPE_FERRY_BOAT_RIGHT = 48; // 0x30
+    field public static final int TYPE_FERRY_TRAIN = 38; // 0x26
+    field public static final int TYPE_FERRY_TRAIN_LEFT = 49; // 0x31
+    field public static final int TYPE_FERRY_TRAIN_RIGHT = 50; // 0x32
+    field public static final int TYPE_FORK_LEFT = 25; // 0x19
+    field public static final int TYPE_FORK_RIGHT = 26; // 0x1a
+    field public static final int TYPE_KEEP_LEFT = 3; // 0x3
+    field public static final int TYPE_KEEP_RIGHT = 4; // 0x4
+    field public static final int TYPE_MERGE_LEFT = 27; // 0x1b
+    field public static final int TYPE_MERGE_RIGHT = 28; // 0x1c
+    field public static final int TYPE_MERGE_SIDE_UNSPECIFIED = 29; // 0x1d
+    field public static final int TYPE_NAME_CHANGE = 2; // 0x2
+    field public static final int TYPE_OFF_RAMP_NORMAL_LEFT = 23; // 0x17
+    field public static final int TYPE_OFF_RAMP_NORMAL_RIGHT = 24; // 0x18
+    field public static final int TYPE_OFF_RAMP_SLIGHT_LEFT = 21; // 0x15
+    field public static final int TYPE_OFF_RAMP_SLIGHT_RIGHT = 22; // 0x16
+    field public static final int TYPE_ON_RAMP_NORMAL_LEFT = 15; // 0xf
+    field public static final int TYPE_ON_RAMP_NORMAL_RIGHT = 16; // 0x10
+    field public static final int TYPE_ON_RAMP_SHARP_LEFT = 17; // 0x11
+    field public static final int TYPE_ON_RAMP_SHARP_RIGHT = 18; // 0x12
+    field public static final int TYPE_ON_RAMP_SLIGHT_LEFT = 13; // 0xd
+    field public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14; // 0xe
+    field public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19; // 0x13
+    field public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20; // 0x14
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34; // 0x22
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35; // 0x23
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32; // 0x20
+    field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33; // 0x21
+    field public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45; // 0x2d
+    field public static final int TYPE_ROUNDABOUT_ENTER_CW = 43; // 0x2b
+    field public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46; // 0x2e
+    field public static final int TYPE_ROUNDABOUT_EXIT_CW = 44; // 0x2c
+    field public static final int TYPE_STRAIGHT = 36; // 0x24
+    field public static final int TYPE_TURN_NORMAL_LEFT = 7; // 0x7
+    field public static final int TYPE_TURN_NORMAL_RIGHT = 8; // 0x8
+    field public static final int TYPE_TURN_SHARP_LEFT = 9; // 0x9
+    field public static final int TYPE_TURN_SHARP_RIGHT = 10; // 0xa
+    field public static final int TYPE_TURN_SLIGHT_LEFT = 5; // 0x5
+    field public static final int TYPE_TURN_SLIGHT_RIGHT = 6; // 0x6
+    field public static final int TYPE_UNKNOWN = 0; // 0x0
+    field public static final int TYPE_U_TURN_LEFT = 11; // 0xb
+    field public static final int TYPE_U_TURN_RIGHT = 12; // 0xc
+  }
+
+  public static final class Maneuver.Builder {
+    ctor public Maneuver.Builder(int);
+    method public androidx.car.app.navigation.model.Maneuver build();
+    method public androidx.car.app.navigation.model.Maneuver.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitAngle(@IntRange(from=1, to=360) int);
+    method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitNumber(@IntRange(from=1) int);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class MapController {
+    method public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+  }
+
+  public static final class MapController.Builder {
+    ctor public MapController.Builder();
+    method public androidx.car.app.navigation.model.MapController build();
+    method public androidx.car.app.navigation.model.MapController.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.MapController.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class MapTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated public androidx.car.app.navigation.model.MapController? getMapController();
+    method @Deprecated public androidx.car.app.model.Pane? getPane();
+  }
+
+  @Deprecated public static final class MapTemplate.Builder {
+    ctor @Deprecated public MapTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setMapController(androidx.car.app.navigation.model.MapController);
+    method @Deprecated public androidx.car.app.navigation.model.MapTemplate.Builder setPane(androidx.car.app.model.Pane);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(7) public final class MapWithContentTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.Template getContentTemplate();
+    method public androidx.car.app.navigation.model.MapController? getMapController();
+  }
+
+  public static final class MapWithContentTemplate.Builder {
+    ctor public MapWithContentTemplate.Builder();
+    method public androidx.car.app.navigation.model.MapWithContentTemplate build();
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setContentTemplate(androidx.car.app.model.Template);
+    method public androidx.car.app.navigation.model.MapWithContentTemplate.Builder setMapController(androidx.car.app.navigation.model.MapController);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+    method public androidx.car.app.model.CarIcon? getImage();
+    method public androidx.car.app.model.CarText? getText();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class MessageInfo.Builder {
+    ctor public MessageInfo.Builder(androidx.car.app.model.CarText);
+    ctor public MessageInfo.Builder(CharSequence);
+    method public androidx.car.app.navigation.model.MessageInfo build();
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setText(androidx.car.app.model.CarText);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setText(CharSequence);
+    method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class NavigationTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public androidx.car.app.model.CarColor? getBackgroundColor();
+    method public androidx.car.app.navigation.model.TravelEstimate? getDestinationTravelEstimate();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method public androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo? getNavigationInfo();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.Toggle? getPanModeToggle();
+  }
+
+  public static final class NavigationTemplate.Builder {
+    ctor public NavigationTemplate.Builder();
+    method public androidx.car.app.navigation.model.NavigationTemplate build();
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setBackgroundColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setDestinationTravelEstimate(androidx.car.app.navigation.model.TravelEstimate);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.NavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.navigation.model.NavigationTemplate.Builder setNavigationInfo(androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo);
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.navigation.model.NavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+  }
+
+  public static interface NavigationTemplate.NavigationInfo {
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(2) public interface PanModeDelegate {
+    method public void sendPanModeChanged(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  public interface PanModeListener {
+    method public void onPanModeChanged(boolean);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PlaceListNavigationTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method @Deprecated public androidx.car.app.model.OnContentRefreshDelegate? getOnContentRefreshDelegate();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @Deprecated public boolean isLoading();
+  }
+
+  @Deprecated public static final class PlaceListNavigationTemplate.Builder {
+    ctor @Deprecated public PlaceListNavigationTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setLoading(boolean);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setOnContentRefreshListener(androidx.car.app.model.OnContentRefreshListener);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method @Deprecated public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(CharSequence);
+  }
+
+  @Deprecated @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class RoutePreviewNavigationTemplate implements androidx.car.app.model.Template {
+    method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.Header? getHeader();
+    method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+    method @Deprecated public androidx.car.app.model.ItemList? getItemList();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.model.ActionStrip? getMapActionStrip();
+    method @Deprecated public androidx.car.app.model.Action? getNavigateAction();
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.PanModeDelegate? getPanModeDelegate();
+    method @Deprecated public androidx.car.app.model.CarText? getTitle();
+    method @Deprecated public boolean isLoading();
+  }
+
+  @Deprecated public static final class RoutePreviewNavigationTemplate.Builder {
+    ctor @Deprecated public RoutePreviewNavigationTemplate.Builder();
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate build();
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeader(androidx.car.app.model.Header);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setLoading(boolean);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setMapActionStrip(androidx.car.app.model.ActionStrip);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setNavigateAction(androidx.car.app.model.Action);
+    method @Deprecated @androidx.car.app.annotations.RequiresCarApi(4) public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setPanModeListener(androidx.car.app.navigation.model.PanModeListener);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(androidx.car.app.model.CarText);
+    method @Deprecated public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+    method public androidx.car.app.model.Distance? getCurrentDistance();
+    method public androidx.car.app.navigation.model.Step? getCurrentStep();
+    method public androidx.car.app.model.CarIcon? getJunctionImage();
+    method public androidx.car.app.navigation.model.Step? getNextStep();
+    method public boolean isLoading();
+  }
+
+  public static final class RoutingInfo.Builder {
+    ctor public RoutingInfo.Builder();
+    method public androidx.car.app.navigation.model.RoutingInfo build();
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setCurrentStep(androidx.car.app.navigation.model.Step, androidx.car.app.model.Distance);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setJunctionImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setLoading(boolean);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setNextStep(androidx.car.app.navigation.model.Step);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Step {
+    method public androidx.car.app.model.CarText? getCue();
+    method public java.util.List<androidx.car.app.navigation.model.Lane!> getLanes();
+    method public androidx.car.app.model.CarIcon? getLanesImage();
+    method public androidx.car.app.navigation.model.Maneuver? getManeuver();
+    method public androidx.car.app.model.CarText? getRoad();
+  }
+
+  public static final class Step.Builder {
+    ctor public Step.Builder();
+    ctor public Step.Builder(androidx.car.app.model.CarText);
+    ctor public Step.Builder(CharSequence);
+    method public androidx.car.app.navigation.model.Step.Builder addLane(androidx.car.app.navigation.model.Lane);
+    method public androidx.car.app.navigation.model.Step build();
+    method public androidx.car.app.navigation.model.Step.Builder setCue(CharSequence);
+    method public androidx.car.app.navigation.model.Step.Builder setLanesImage(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.navigation.model.Step.Builder setManeuver(androidx.car.app.navigation.model.Maneuver);
+    method public androidx.car.app.navigation.model.Step.Builder setRoad(CharSequence);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class TravelEstimate {
+    method public androidx.car.app.model.DateTimeWithZone? getArrivalTimeAtDestination();
+    method public androidx.car.app.model.Distance? getRemainingDistance();
+    method public androidx.car.app.model.CarColor? getRemainingDistanceColor();
+    method public androidx.car.app.model.CarColor? getRemainingTimeColor();
+    method public long getRemainingTimeSeconds();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.CarIcon? getTripIcon();
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.model.CarText? getTripText();
+    field public static final long REMAINING_TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+  }
+
+  public static final class TravelEstimate.Builder {
+    ctor public TravelEstimate.Builder(androidx.car.app.model.Distance, androidx.car.app.model.DateTimeWithZone);
+    ctor @RequiresApi(26) public TravelEstimate.Builder(androidx.car.app.model.Distance, java.time.ZonedDateTime);
+    method public androidx.car.app.navigation.model.TravelEstimate build();
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingDistanceColor(androidx.car.app.model.CarColor);
+    method @RequiresApi(26) public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTime(java.time.Duration);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeSeconds(@IntRange(from=0xffffffff) long);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.TravelEstimate.Builder setTripIcon(androidx.car.app.model.CarIcon);
+    method @androidx.car.app.annotations.RequiresCarApi(5) public androidx.car.app.navigation.model.TravelEstimate.Builder setTripText(androidx.car.app.model.CarText);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Trip {
+    method public androidx.car.app.model.CarText? getCurrentRoad();
+    method public java.util.List<androidx.car.app.navigation.model.TravelEstimate!> getDestinationTravelEstimates();
+    method public java.util.List<androidx.car.app.navigation.model.Destination!> getDestinations();
+    method public java.util.List<androidx.car.app.navigation.model.TravelEstimate!> getStepTravelEstimates();
+    method public java.util.List<androidx.car.app.navigation.model.Step!> getSteps();
+    method public boolean isLoading();
+  }
+
+  public static final class Trip.Builder {
+    ctor public Trip.Builder();
+    method public androidx.car.app.navigation.model.Trip.Builder addDestination(androidx.car.app.navigation.model.Destination, androidx.car.app.navigation.model.TravelEstimate);
+    method public androidx.car.app.navigation.model.Trip.Builder addStep(androidx.car.app.navigation.model.Step, androidx.car.app.navigation.model.TravelEstimate);
+    method public androidx.car.app.navigation.model.Trip build();
+    method public androidx.car.app.navigation.model.Trip.Builder setCurrentRoad(CharSequence);
+    method public androidx.car.app.navigation.model.Trip.Builder setLoading(boolean);
+  }
+
+}
+
+package androidx.car.app.notification {
+
+  public final class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
+    ctor public CarAppExtender(android.app.Notification);
+    method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+    method public java.util.List<android.app.Notification.Action!> getActions();
+    method public String? getChannelId();
+    method public androidx.car.app.model.CarColor? getColor();
+    method public android.app.PendingIntent? getContentIntent();
+    method public CharSequence? getContentText();
+    method public CharSequence? getContentTitle();
+    method public android.app.PendingIntent? getDeleteIntent();
+    method public int getImportance();
+    method public android.graphics.Bitmap? getLargeIcon();
+    method @DrawableRes public int getSmallIcon();
+    method public static boolean isExtended(android.app.Notification);
+  }
+
+  public static final class CarAppExtender.Builder {
+    ctor public CarAppExtender.Builder();
+    method public androidx.car.app.notification.CarAppExtender.Builder addAction(@DrawableRes int, CharSequence, android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender build();
+    method public androidx.car.app.notification.CarAppExtender.Builder setChannelId(String);
+    method public androidx.car.app.notification.CarAppExtender.Builder setColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentIntent(android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentText(CharSequence);
+    method public androidx.car.app.notification.CarAppExtender.Builder setContentTitle(CharSequence);
+    method public androidx.car.app.notification.CarAppExtender.Builder setDeleteIntent(android.app.PendingIntent);
+    method public androidx.car.app.notification.CarAppExtender.Builder setImportance(int);
+    method public androidx.car.app.notification.CarAppExtender.Builder setLargeIcon(android.graphics.Bitmap);
+    method public androidx.car.app.notification.CarAppExtender.Builder setSmallIcon(int);
+  }
+
+  public final class CarNotificationManager {
+    method public boolean areNotificationsEnabled();
+    method public void cancel(int);
+    method public void cancel(String?, int);
+    method public void cancelAll();
+    method public void createNotificationChannel(androidx.core.app.NotificationChannelCompat);
+    method public void createNotificationChannelGroup(androidx.core.app.NotificationChannelGroupCompat);
+    method public void createNotificationChannelGroups(java.util.List<androidx.core.app.NotificationChannelGroupCompat!>);
+    method public void createNotificationChannels(java.util.List<androidx.core.app.NotificationChannelCompat!>);
+    method public void deleteNotificationChannel(String);
+    method public void deleteNotificationChannelGroup(String);
+    method public void deleteUnlistedNotificationChannels(java.util.Collection<java.lang.String!>);
+    method public static androidx.car.app.notification.CarNotificationManager from(android.content.Context);
+    method public static java.util.Set<java.lang.String!> getEnabledListenerPackages(android.content.Context);
+    method public int getImportance();
+    method public androidx.core.app.NotificationChannelCompat? getNotificationChannel(String);
+    method public androidx.core.app.NotificationChannelCompat? getNotificationChannel(String, String);
+    method public androidx.core.app.NotificationChannelGroupCompat? getNotificationChannelGroup(String);
+    method public java.util.List<androidx.core.app.NotificationChannelGroupCompat!> getNotificationChannelGroups();
+    method public java.util.List<androidx.core.app.NotificationChannelCompat!> getNotificationChannels();
+    method public void notify(int, androidx.core.app.NotificationCompat.Builder);
+    method public void notify(String?, int, androidx.core.app.NotificationCompat.Builder);
+  }
+
+  public final class CarPendingIntent {
+    method public static android.app.PendingIntent getCarApp(android.content.Context, int, android.content.Intent, int);
+  }
+
+}
+
+package androidx.car.app.serialization {
+
+  public final class Bundleable implements android.os.Parcelable {
+    method public static androidx.car.app.serialization.Bundleable create(Object) throws androidx.car.app.serialization.BundlerException;
+    method public int describeContents();
+    method public Object get() throws androidx.car.app.serialization.BundlerException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<androidx.car.app.serialization.Bundleable!> CREATOR;
+  }
+
+  public class BundlerException extends java.lang.Exception {
+    ctor public BundlerException(String?);
+    ctor public BundlerException(String?, Throwable);
+  }
+
+  @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+    method public int getSize();
+    method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+    property public abstract int size;
+  }
+
+}
+
+package androidx.car.app.suggestion {
+
+  @androidx.car.app.annotations.RequiresCarApi(5) public class SuggestionManager implements androidx.car.app.managers.Manager {
+    method @MainThread public void updateSuggestions(java.util.List<androidx.car.app.suggestion.model.Suggestion!>);
+  }
+
+}
+
+package androidx.car.app.suggestion.model {
+
+  @SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class Suggestion {
+    method public android.app.PendingIntent? getAction();
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getIdentifier();
+    method public androidx.car.app.model.CarText? getSubtitle();
+    method public androidx.car.app.model.CarText getTitle();
+  }
+
+  public static final class Suggestion.Builder {
+    ctor public Suggestion.Builder();
+    method public androidx.car.app.suggestion.model.Suggestion build();
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setAction(android.app.PendingIntent);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setIdentifier(String);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setSubtitle(CharSequence);
+    method public androidx.car.app.suggestion.model.Suggestion.Builder setTitle(CharSequence);
+  }
+
+}
+
+package androidx.car.app.validation {
+
+  public final class HostValidator {
+    method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getAllowedHosts();
+    method public boolean isValidHost(androidx.car.app.HostInfo);
+    field public static final androidx.car.app.validation.HostValidator ALLOW_ALL_HOSTS_VALIDATOR;
+    field public static final String TEMPLATE_RENDERER_PERMISSION = "android.car.permission.TEMPLATE_RENDERER";
+  }
+
+  public static final class HostValidator.Builder {
+    ctor public HostValidator.Builder(android.content.Context);
+    method public androidx.car.app.validation.HostValidator.Builder addAllowedHost(String, String);
+    method public androidx.car.app.validation.HostValidator.Builder addAllowedHosts(@ArrayRes int);
+    method public androidx.car.app.validation.HostValidator build();
+  }
+
+}
+
+package androidx.car.app.versioning {
+
+  public final class CarAppApiLevels {
+    method public static int getLatest();
+    method public static int getOldest();
+    field public static final int LEVEL_1 = 1; // 0x1
+    field public static final int LEVEL_2 = 2; // 0x2
+    field public static final int LEVEL_3 = 3; // 0x3
+    field public static final int LEVEL_4 = 4; // 0x4
+    field public static final int LEVEL_5 = 5; // 0x5
+    field public static final int LEVEL_6 = 6; // 0x6
+    field public static final int LEVEL_7 = 7; // 0x7
+    field public static final int LEVEL_8 = 8; // 0x8
+  }
+
+}
+
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index ea44979..0d0e4ff 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -9,10 +9,14 @@
     Added method androidx.compose.foundation.gestures.DraggableAnchors.minPosition()
 AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#positionAt(int):
     Added method androidx.compose.foundation.gestures.DraggableAnchors.positionAt(int)
+AddedAbstractMethod: androidx.compose.foundation.lazy.LazyListScope#stickyHeader(Object, Object, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit>):
+    Added method androidx.compose.foundation.lazy.LazyListScope.stickyHeader(Object,Object,kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit>)
 AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getSpan():
     Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getSpan()
 AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMaxSpan():
     Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMaxSpan()
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridScope#stickyHeader(Object, Object, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit>):
+    Added method androidx.compose.foundation.lazy.grid.LazyGridScope.stickyHeader(Object,Object,kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit>)
 
 
 ParameterNameChange: androidx.compose.foundation.gestures.DraggableAnchors#positionOf(T) parameter #0:
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 8f80daa..88a3de5 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -896,7 +896,8 @@
     method @Deprecated public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public default void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
     method @Deprecated public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
+    method @Deprecated public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> content);
   }
 
   @androidx.compose.runtime.Stable public final class LazyListState implements androidx.compose.foundation.gestures.ScrollableState {
@@ -1061,6 +1062,7 @@
   @androidx.compose.foundation.lazy.grid.LazyGridScopeMarker public sealed interface LazyGridScope {
     method public void item(optional Object? key, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridItemSpanScope,androidx.compose.foundation.lazy.grid.GridItemSpan>? span, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemSpanScope,? super java.lang.Integer,androidx.compose.foundation.lazy.grid.GridItemSpan>? span, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit> content);
   }
 
   @kotlin.DslMarker public @interface LazyGridScopeMarker {
@@ -1301,7 +1303,7 @@
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
-  public final class LazyStaggeredGridState implements androidx.compose.foundation.gestures.ScrollableState {
+  @androidx.compose.runtime.Stable public final class LazyStaggeredGridState implements androidx.compose.foundation.gestures.ScrollableState {
     ctor public LazyStaggeredGridState(optional int initialFirstVisibleItemIndex, optional int initialFirstVisibleItemOffset);
     method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public float dispatchRawDelta(float delta);
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index ea44979..0d0e4ff 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -9,10 +9,14 @@
     Added method androidx.compose.foundation.gestures.DraggableAnchors.minPosition()
 AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#positionAt(int):
     Added method androidx.compose.foundation.gestures.DraggableAnchors.positionAt(int)
+AddedAbstractMethod: androidx.compose.foundation.lazy.LazyListScope#stickyHeader(Object, Object, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit>):
+    Added method androidx.compose.foundation.lazy.LazyListScope.stickyHeader(Object,Object,kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit>)
 AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getSpan():
     Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getSpan()
 AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMaxSpan():
     Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMaxSpan()
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridScope#stickyHeader(Object, Object, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit>):
+    Added method androidx.compose.foundation.lazy.grid.LazyGridScope.stickyHeader(Object,Object,kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit>)
 
 
 ParameterNameChange: androidx.compose.foundation.gestures.DraggableAnchors#positionOf(T) parameter #0:
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 46ea9c3..c0be53e 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -898,7 +898,8 @@
     method @Deprecated public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public default void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
     method @Deprecated public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
+    method @Deprecated public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> content);
   }
 
   @androidx.compose.runtime.Stable public final class LazyListState implements androidx.compose.foundation.gestures.ScrollableState {
@@ -1063,6 +1064,7 @@
   @androidx.compose.foundation.lazy.grid.LazyGridScopeMarker public sealed interface LazyGridScope {
     method public void item(optional Object? key, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridItemSpanScope,androidx.compose.foundation.lazy.grid.GridItemSpan>? span, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemSpanScope,? super java.lang.Integer,androidx.compose.foundation.lazy.grid.GridItemSpan>? span, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method public void stickyHeader(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.grid.LazyGridItemScope,? super java.lang.Integer,kotlin.Unit> content);
   }
 
   @kotlin.DslMarker public @interface LazyGridScopeMarker {
@@ -1303,7 +1305,7 @@
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.Object?> contentType, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
-  public final class LazyStaggeredGridState implements androidx.compose.foundation.gestures.ScrollableState {
+  @androidx.compose.runtime.Stable public final class LazyStaggeredGridState implements androidx.compose.foundation.gestures.ScrollableState {
     ctor public LazyStaggeredGridState(optional int initialFirstVisibleItemIndex, optional int initialFirstVisibleItemOffset);
     method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public float dispatchRawDelta(float delta);
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt
index 1b99bd8..919e277 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt
@@ -64,6 +64,18 @@
     }
 
     @Test
+    fun scrollProgrammatically_useStickyHeader() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                useStickyHeader = true
+            )
+        }
+    }
+
+    @Test
     fun scrollProgrammatically_newItemComposed() {
         benchmarkRule.toggleStateBenchmark {
             GridRemeasureTestCase(
@@ -87,6 +99,19 @@
     }
 
     @Test
+    fun scrollViaPointerInput_useStickyHeader() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                usePointerInput = true,
+                useStickyHeader = true
+            )
+        }
+    }
+
+    @Test
     fun scrollViaPointerInput_newItemComposed() {
         benchmarkRule.toggleStateBenchmark {
             GridRemeasureTestCase(
@@ -142,7 +167,7 @@
 class LazyGridScrollingTestCase(
     private val name: String,
     val isVertical: Boolean,
-    val content: @Composable GridRemeasureTestCase.(LazyGridState) -> Unit
+    val content: @Composable GridRemeasureTestCase.(LazyGridState, Boolean) -> Unit
 ) {
     override fun toString(): String {
         return name
@@ -150,36 +175,45 @@
 }
 
 private val Vertical =
-    LazyGridScrollingTestCase("Vertical", isVertical = true) { state ->
+    LazyGridScrollingTestCase("Vertical", isVertical = true) { state, useStickyHeader ->
         LazyVerticalGrid(
             columns = GridCells.Fixed(2),
             state = state,
             modifier = Modifier.requiredHeight(400.dp).fillMaxWidth(),
             flingBehavior = NoFlingBehavior
         ) {
-            items(2) { FirstLargeItem() }
+            if (useStickyHeader) {
+                stickyHeader { FirstLargeItem() }
+            } else {
+                items(2) { FirstLargeItem() }
+            }
             items(items) { RegularItem() }
         }
     }
 
 private val Horizontal =
-    LazyGridScrollingTestCase("Horizontal", isVertical = false) { state ->
+    LazyGridScrollingTestCase("Horizontal", isVertical = false) { state, useStickyHeader ->
         LazyHorizontalGrid(
             rows = GridCells.Fixed(2),
             state = state,
             modifier = Modifier.requiredWidth(400.dp).fillMaxHeight(),
             flingBehavior = NoFlingBehavior
         ) {
-            items(2) { FirstLargeItem() }
+            if (useStickyHeader) {
+                stickyHeader { FirstLargeItem() }
+            } else {
+                items(2) { FirstLargeItem() }
+            }
             items(items) { RegularItem() }
         }
     }
 
 class GridRemeasureTestCase(
     val addNewItemOnToggle: Boolean,
-    val content: @Composable GridRemeasureTestCase.(LazyGridState) -> Unit,
+    val content: @Composable GridRemeasureTestCase.(LazyGridState, Boolean) -> Unit,
     val isVertical: Boolean,
-    val usePointerInput: Boolean = false
+    val usePointerInput: Boolean = false,
+    val useStickyHeader: Boolean = false
 ) : LazyBenchmarkTestCase(isVertical, usePointerInput) {
 
     val items = List(300) { LazyItem(it) }
@@ -201,7 +235,7 @@
             }
         InitializeScrollHelper(scrollAmount = scrollBy)
         state = rememberLazyGridState()
-        content(state)
+        content(state, useStickyHeader)
     }
 
     @Composable
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index a2d29f1..a328cd2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -71,7 +71,9 @@
 import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
 import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.samples.StickyHeaderSample
+import androidx.compose.foundation.samples.StickyHeaderGridSample
+import androidx.compose.foundation.samples.StickyHeaderHeaderIndexSample
+import androidx.compose.foundation.samples.StickyHeaderListSample
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.integration.demos.common.ComposableDemo
@@ -134,7 +136,11 @@
         ComposableDemo("Rtl list") { RtlListDemo() },
         ComposableDemo("LazyColumn DSL") { LazyColumnScope() },
         ComposableDemo("LazyRow DSL") { LazyRowScope() },
-        ComposableDemo("LazyColumn with sticky headers") { StickyHeaderSample() },
+        ComposableDemo("LazyColumn with sticky headers") { StickyHeaderListSample() },
+        ComposableDemo("LazyVerticalGrid with sticky headers") { StickyHeaderGridSample() },
+        ComposableDemo("LazyColumn with sticky headers - header index") {
+            StickyHeaderHeaderIndexSample()
+        },
         ComposableDemo("Arrangements") { LazyListArrangements() },
         ComposableDemo("ReverseLayout and RTL") { ReverseLayoutAndRtlDemo() },
         ComposableDemo("Nested lazy lists") { NestedLazyDemo() },
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridHeadersTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridHeadersTest.kt
new file mode 100644
index 0000000..eda4457
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridHeadersTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.grid
+
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.list.scrollBy
+import androidx.compose.foundation.lazy.list.setContentWithTestViewConfiguration
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyGridHeadersTest {
+
+    private val LazyGridTag = "LazyGrid"
+
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun lazyVerticalGridShowsHeader() {
+        val items = (1..6).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+
+        rule.setContent {
+            LazyVerticalGrid(columns = GridCells.Fixed(3), modifier = Modifier.height(300.dp)) {
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(firstHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(it)) }
+
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(secondHeaderTag))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(firstHeaderTag).assertIsDisplayed()
+
+        rule.onNodeWithTag("1").assertIsDisplayed()
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertDoesNotExist()
+    }
+
+    @Test
+    fun lazyVerticalGridShowsHeadersOnScroll() {
+        val items = (1..3).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+        lateinit var state: LazyGridState
+
+        rule.setContentWithTestViewConfiguration {
+            LazyVerticalGrid(
+                columns = GridCells.Fixed(3),
+                modifier = Modifier.height(300.dp).testTag(LazyGridTag),
+                state = rememberLazyGridState().also { state = it }
+            ) {
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(firstHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(it)) }
+
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(secondHeaderTag))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(LazyGridTag).scrollBy(y = 102.dp, density = rule.density)
+
+        rule
+            .onNodeWithTag(firstHeaderTag)
+            .assertIsDisplayed()
+            .assertTopPositionInRootIsEqualTo(0.dp)
+
+        rule.runOnIdle {
+            Assert.assertEquals(0, state.layoutInfo.visibleItemsInfo.first().index)
+            Assert.assertEquals(IntOffset.Zero, state.layoutInfo.visibleItemsInfo.first().offset)
+        }
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertIsDisplayed()
+    }
+
+    @Test
+    fun lazyVerticalGridHeaderIsReplaced() {
+        val items = (1..6).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+
+        rule.setContentWithTestViewConfiguration {
+            LazyVerticalGrid(
+                columns = GridCells.Fixed(3),
+                modifier = Modifier.height(300.dp).testTag(LazyGridTag)
+            ) {
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(firstHeaderTag))
+                }
+
+                stickyHeader {
+                    Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(secondHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.height(101.dp).fillMaxWidth().testTag(it)) }
+            }
+        }
+
+        rule.onNodeWithTag(LazyGridTag).scrollBy(y = 105.dp, density = rule.density)
+
+        rule.onNodeWithTag(firstHeaderTag).assertIsNotDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertIsDisplayed()
+
+        rule.onNodeWithTag("1").assertIsDisplayed()
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+    }
+
+    @Test
+    fun lazyHorizontalGridShowsHeader() {
+        val items = (1..6).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+
+        rule.setContent {
+            LazyHorizontalGrid(rows = GridCells.Fixed(3), modifier = Modifier.width(300.dp)) {
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(firstHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(it)) }
+
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(secondHeaderTag))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(firstHeaderTag).assertIsDisplayed()
+
+        rule.onNodeWithTag("1").assertIsDisplayed()
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertDoesNotExist()
+    }
+
+    @Test
+    fun lazyHorizontalGridShowsHeadersOnScroll() {
+        val items = (1..3).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+        lateinit var state: LazyGridState
+
+        rule.setContentWithTestViewConfiguration {
+            LazyHorizontalGrid(
+                rows = GridCells.Fixed(3),
+                modifier = Modifier.width(300.dp).testTag(LazyGridTag),
+                state = rememberLazyGridState().also { state = it }
+            ) {
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(firstHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(it)) }
+
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(secondHeaderTag))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(LazyGridTag).scrollBy(x = 102.dp, density = rule.density)
+
+        rule
+            .onNodeWithTag(firstHeaderTag)
+            .assertIsDisplayed()
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+
+        rule.runOnIdle {
+            Assert.assertEquals(0, state.layoutInfo.visibleItemsInfo.first().index)
+            Assert.assertEquals(IntOffset.Zero, state.layoutInfo.visibleItemsInfo.first().offset)
+        }
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertIsDisplayed()
+    }
+
+    @Test
+    fun lazyHorizontalGridHeaderIsReplaced() {
+        val items = (1..6).map { it.toString() }
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+
+        rule.setContentWithTestViewConfiguration {
+            LazyHorizontalGrid(
+                rows = GridCells.Fixed(3),
+                modifier = Modifier.width(300.dp).testTag(LazyGridTag)
+            ) {
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(firstHeaderTag))
+                }
+
+                stickyHeader {
+                    Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(secondHeaderTag))
+                }
+
+                items(items) { Spacer(Modifier.width(101.dp).fillMaxHeight().testTag(it)) }
+            }
+        }
+
+        rule.onNodeWithTag(LazyGridTag).scrollBy(x = 105.dp, density = rule.density)
+
+        rule.onNodeWithTag(firstHeaderTag).assertIsNotDisplayed()
+
+        rule.onNodeWithTag(secondHeaderTag).assertIsDisplayed()
+
+        rule.onNodeWithTag("1").assertIsDisplayed()
+
+        rule.onNodeWithTag("2").assertIsDisplayed()
+    }
+
+    @Test
+    fun headerIsDisplayedWhenItIsFullyInContentPadding() {
+        val headerTag = "header"
+        val itemIndexPx = 100
+        val itemIndexDp = with(rule.density) { itemIndexPx.toDp() }
+        lateinit var state: LazyGridState
+
+        rule.setContent {
+            LazyVerticalGrid(
+                columns = GridCells.Fixed(3),
+                modifier = Modifier.requiredSize(itemIndexDp * 4),
+                state = rememberLazyGridState().also { state = it },
+                contentPadding = PaddingValues(top = itemIndexDp * 2)
+            ) {
+                stickyHeader { Spacer(Modifier.requiredSize(itemIndexDp).testTag(headerTag)) }
+
+                items((0..11).toList()) {
+                    Spacer(Modifier.requiredSize(itemIndexDp).testTag("$it"))
+                }
+            }
+        }
+
+        rule.runOnIdle { runBlocking { state.scrollToItem(1, itemIndexPx / 2) } }
+
+        rule.onNodeWithTag(headerTag).assertTopPositionInRootIsEqualTo(itemIndexDp / 2)
+
+        rule.runOnIdle {
+            Assert.assertEquals(0, state.layoutInfo.visibleItemsInfo.first().index)
+            Assert.assertEquals(
+                itemIndexPx / 2 - /* content padding size */ itemIndexPx * 2,
+                state.layoutInfo.visibleItemsInfo.first().offset.y
+            )
+        }
+
+        rule.onNodeWithTag("0").assertTopPositionInRootIsEqualTo(itemIndexDp * 3 / 2)
+    }
+
+    @Test
+    fun lazyVerticalGridShowsHeader2() {
+        val firstHeaderTag = "firstHeaderTag"
+        val secondHeaderTag = "secondHeaderTag"
+        val itemSizeDp = with(rule.density) { 100.toDp() }
+        val scrollDistance = 20
+        val scrollDistanceDp = with(rule.density) { scrollDistance.toDp() }
+        val state = LazyGridState()
+
+        rule.setContent {
+            LazyVerticalGrid(
+                columns = GridCells.Fixed(3),
+                modifier = Modifier.height(itemSizeDp * 3.5f),
+                state = state
+            ) {
+                stickyHeader {
+                    Spacer(Modifier.height(itemSizeDp).fillMaxWidth().testTag(firstHeaderTag))
+                }
+                stickyHeader {
+                    Spacer(Modifier.height(itemSizeDp).fillMaxWidth().testTag(secondHeaderTag))
+                }
+
+                items(100) {
+                    Spacer(Modifier.height(itemSizeDp).fillMaxWidth().testTag(it.toString()))
+                }
+            }
+        }
+
+        rule.runOnIdle { runBlocking { state.scrollBy(scrollDistance.toFloat()) } }
+
+        rule.onNodeWithTag(firstHeaderTag).assertTopPositionInRootIsEqualTo(-scrollDistanceDp)
+        rule
+            .onNodeWithTag(secondHeaderTag)
+            .assertTopPositionInRootIsEqualTo(itemSizeDp - scrollDistanceDp)
+        rule.onNodeWithTag("0").assertTopPositionInRootIsEqualTo(itemSizeDp * 2 - scrollDistanceDp)
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemAppearanceAnimationTest.kt
index d19125a..742b1d3 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemAppearanceAnimationTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -292,6 +293,20 @@
         }
     }
 
+    @Test
+    fun snapToPosition_noAnimation() {
+        val items = List(200) { Color.Black }
+        rule.setContent {
+            LazyGrid(containerSize = itemSizeDp) {
+                items(items, key = { it.toArgb() }) { Item(it) }
+            }
+        }
+
+        rule.runOnIdle { runBlocking { state.scrollToItem(200) } }
+
+        assertPixels(itemSize) { _, _ -> Color.Black }
+    }
+
     private fun assertPixels(
         mainAxisSize: Int,
         crossAxisSize: Int = this.crossAxisSize,
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
index 3dc7c6d..da8133b 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
@@ -333,6 +333,20 @@
         }
     }
 
+    @Test
+    fun snapToPosition_noAnimation() {
+        val items = List(200) { Color.Black }
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp) {
+                items(items, key = { it.toArgb() }) { Item(it) }
+            }
+        }
+
+        rule.runOnIdle { runBlocking { state.scrollToItem(200) } }
+
+        assertPixels(itemSize) { Color.Black }
+    }
+
     @Composable
     private fun LazyList(
         containerSize: Dp? = containerSizeDp,
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemAppearanceAnimationTest.kt
index 114c6b5..ba5757e 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemAppearanceAnimationTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -290,6 +291,20 @@
         }
     }
 
+    @Test
+    fun snapToPosition_noAnimation() {
+        val items = List(200) { Color.Black }
+        rule.setContent {
+            LazyGrid(containerSize = itemSizeDp) {
+                items(items, key = { it.toArgb() }) { Item(it) }
+            }
+        }
+
+        rule.runOnIdle { runBlocking { state.scrollToItem(200) } }
+
+        assertPixels(itemSize) { _, _ -> Color.Black }
+    }
+
     private fun assertPixels(
         mainAxisSize: Int,
         crossAxisSize: Int = this.crossAxisSize,
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 5dc0573..b5f57f0 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -18,18 +18,20 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -39,6 +41,7 @@
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -48,6 +51,7 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.launch
 
@@ -81,10 +85,9 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
-fun StickyHeaderSample() {
+fun StickyHeaderListSample() {
     val sections = listOf("A", "B", "C", "D", "E", "F", "G")
 
     LazyColumn(reverseLayout = true, contentPadding = PaddingValues(6.dp)) {
@@ -102,6 +105,59 @@
 
 @Sampled
 @Composable
+@Preview
+fun StickyHeaderGridSample() {
+    val sections = listOf("A", "B", "C", "D", "E", "F", "G")
+
+    LazyVerticalGrid(columns = GridCells.Fixed(3), contentPadding = PaddingValues(6.dp)) {
+        sections.forEach { section ->
+            stickyHeader {
+                Text(
+                    "Section $section",
+                    Modifier.fillMaxWidth().background(Color.LightGray).padding(8.dp)
+                )
+            }
+            items(10) { Text("Item= $it S=$section", modifier = Modifier.height(64.dp)) }
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun StickyHeaderHeaderIndexSample() {
+    /**
+     * Checks if [index] is in the sticking position, that is, it's the first visible item and its
+     * offset is equal to the content padding.
+     */
+    fun LazyListState.isSticking(index: Int): State<Boolean> {
+        return derivedStateOf {
+            val firstVisible = layoutInfo.visibleItemsInfo.firstOrNull()
+            firstVisible?.index == index && firstVisible.offset == -layoutInfo.beforeContentPadding
+        }
+    }
+
+    val sections = listOf("A", "B", "C", "D", "E", "F", "G")
+    val state = rememberLazyListState()
+
+    LazyColumn(state = state, reverseLayout = true, contentPadding = PaddingValues(6.dp)) {
+        sections.forEach { section ->
+            stickyHeader { headerIndex ->
+                // change color when header is sticking
+                val isSticking by remember(state) { state.isSticking(headerIndex) }
+                Text(
+                    "Section $section",
+                    Modifier.fillMaxWidth()
+                        .background(if (isSticking) Color.Red else Color.LightGray)
+                        .padding(8.dp)
+                )
+            }
+            items(10) { Text("Item $it from the section $section") }
+        }
+    }
+}
+
+@Sampled
+@Composable
 fun AnimateItemSample() {
     var list by remember { mutableStateOf(listOf("1", "2", "3")) }
     Column {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 37d4eb5..b32b3b2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -5338,6 +5338,28 @@
             )
         }
     }
+
+    /** Regression test for b/358572550 */
+    @Test
+    fun disabledParentClickable_doesNotCrashOnFocusEvent() {
+        val tag = "testClickable"
+        val focusRequester = FocusRequester()
+        lateinit var inputModeManager: InputModeManager
+        rule.setContent {
+            inputModeManager = LocalInputModeManager.current
+            Box(Modifier.size(100.dp).clickable(enabled = false, onClick = {})) {
+                // Any focus target inside would work, but because clickable merges descendants it
+                // prevents itself from being merged into the parent node so it is easier to test
+                Box(Modifier.size(10.dp).focusRequester(focusRequester).testTag(tag).clickable {})
+            }
+        }
+        rule.runOnIdle { inputModeManager.requestInputMode(Keyboard) }
+
+        // Should not crash
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        rule.onNodeWithTag(tag).assertIsFocused()
+    }
 }
 
 /**
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
index 151bf4c..6ce3b4a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
@@ -905,5 +905,5 @@
 }
 
 /** Average of the min time and timeout for the delay between clicks for a double click */
-private val InjectionScope.doubleTapDelay
+internal val InjectionScope.doubleTapDelay
     get() = with(viewConfiguration) { (doubleTapMinTimeMillis + doubleTapTimeoutMillis) / 2 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 165c8c5..c2529e9 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -666,29 +666,175 @@
     }
 
     @Test
-    fun doubleClick() {
-        var counter = 0
-        val onClick: () -> Unit = { ++counter }
-
+    fun doubleClick_withinTimeout_aboveMinimumDuration() {
+        var clickCounter = 0
+        var doubleClickCounter = 0
         rule.setContent {
-            Box {
-                BasicText(
-                    "ClickableText",
-                    modifier =
-                        Modifier.testTag("myClickable").combinedClickable(
-                            onDoubleClick = onClick
-                        ) {}
-                )
-            }
+            BasicText(
+                "ClickableText",
+                modifier =
+                    Modifier.testTag("myClickable")
+                        .combinedClickable(
+                            onDoubleClick = { ++doubleClickCounter },
+                            onClick = { ++clickCounter }
+                        )
+            )
         }
 
-        rule.onNodeWithTag("myClickable").performTouchInput { doubleClick() }
+        rule.onNodeWithTag("myClickable").performTouchInput {
+            down(center)
+            up()
+            advanceEventTime(doubleTapDelay)
+            down(center)
+            up()
+        }
 
-        rule.mainClock.advanceTimeUntil { counter == 1 }
+        // Double click should not trigger click, and the double click should be immediately invoked
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(0)
+            assertThat(doubleClickCounter).isEqualTo(1)
+        }
+    }
 
-        rule.onNodeWithTag("myClickable").performTouchInput { doubleClick() }
+    @Test
+    fun doubleClick_withinTimeout_belowMinimumDuration() {
+        var clickCounter = 0
+        var doubleClickCounter = 0
+        rule.setContent {
+            BasicText(
+                "ClickableText",
+                modifier =
+                    Modifier.testTag("myClickable")
+                        .combinedClickable(
+                            onDoubleClick = { ++doubleClickCounter },
+                            onClick = { ++clickCounter }
+                        )
+            )
+        }
 
-        rule.mainClock.advanceTimeUntil { counter == 2 }
+        var doubleTapTimeoutDelay: Long = 0
+
+        rule.onNodeWithTag("myClickable").performTouchInput {
+            doubleTapTimeoutDelay = viewConfiguration.doubleTapTimeoutMillis + 100
+            down(center)
+            up()
+            // Send a second press below the minimum time required for a double tap
+            val minimumDuration = viewConfiguration.doubleTapMinTimeMillis
+            advanceEventTime(minimumDuration / 2)
+            down(center)
+            up()
+        }
+
+        // Because the second tap was below the timeout, it is ignored, and so no click is invoked /
+        // we are still waiting for a second tap to trigger the double click
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(0)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+
+        // After the timeout has run out, the first click will be invoked, and no double click will
+        // be invoked
+        rule.mainClock.advanceTimeBy(doubleTapTimeoutDelay)
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(1)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun doubleClick_outsideTimeout() {
+        var clickCounter = 0
+        var doubleClickCounter = 0
+        rule.setContent {
+            BasicText(
+                "ClickableText",
+                modifier =
+                    Modifier.testTag("myClickable")
+                        .combinedClickable(
+                            onDoubleClick = { ++doubleClickCounter },
+                            onClick = { ++clickCounter }
+                        )
+            )
+        }
+
+        var delay: Long = 0
+
+        rule.onNodeWithTag("myClickable").performTouchInput {
+            // Delay slightly past the timeout
+            delay = viewConfiguration.doubleTapTimeoutMillis + 100
+            down(center)
+            up()
+        }
+
+        // The click should not be invoked until the timeout has run out
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(0)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+
+        // After the timeout has run out, the click will be invoked
+        rule.mainClock.advanceTimeBy(delay)
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(1)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+
+        // Perform a second click, after the timeout has elapsed - this should not trigger a double
+        // click
+        rule.onNodeWithTag("myClickable").performTouchInput {
+            down(center)
+            up()
+        }
+
+        // The second click should not be invoked until the timeout has run out
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(1)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+
+        // After the timeout has run out, the second click will be invoked, and no double click will
+        // be invoked
+        rule.mainClock.advanceTimeBy(delay)
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(2)
+            assertThat(doubleClickCounter).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun doubleClick_secondClickIsALongClick() {
+        var clickCounter = 0
+        var doubleClickCounter = 0
+        var longClickCounter = 0
+        rule.setContent {
+            BasicText(
+                "ClickableText",
+                modifier =
+                    Modifier.testTag("myClickable")
+                        .combinedClickable(
+                            onDoubleClick = { ++doubleClickCounter },
+                            onClick = { ++clickCounter },
+                            onLongClick = { ++longClickCounter }
+                        )
+            )
+        }
+
+        rule.onNodeWithTag("myClickable").performTouchInput {
+            down(center)
+            up()
+            advanceEventTime(doubleTapDelay)
+            down(center)
+        }
+
+        // Wait for the long click
+        rule.mainClock.advanceTimeBy(1000)
+
+        // Long click should cancel double click and click
+        rule.runOnIdle {
+            assertThat(clickCounter).isEqualTo(0)
+            assertThat(doubleClickCounter).isEqualTo(0)
+            assertThat(longClickCounter).isEqualTo(1)
+        }
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
index 253dd19..cb0ca97 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
@@ -749,7 +749,10 @@
 
         rule.mainClock.advanceTimeBy(LongPressTimeoutMillis + 10)
 
-        assertTrue(tapped)
+        // The first tap was part of a double tap, which then became a long press, so we don't
+        // retroactively treat it as a tap to avoid triggering both a tap and a long press at the
+        // same time
+        assertFalse(tapped)
         assertTrue(longPressed)
         assertFalse(released)
         assertFalse(canceled)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt
index 831beb3..cd855cb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt
@@ -26,6 +26,7 @@
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.TouchInjectionScope
 import androidx.compose.ui.test.invokeGlobalAssertions
+import androidx.compose.ui.test.tryPerformAccessibilityChecks
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.center
@@ -189,6 +190,7 @@
     block: TouchInjectionScope.() -> Unit
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed to inject stylus input.")
     val stylusInjectionScope = HandwritingTestStylusInjectScope(node)
     block.invoke(stylusInjectionScope)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
index caecd44..99e074f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
@@ -48,6 +48,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.testutils.assertPixelColor
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusManager
@@ -58,6 +59,7 @@
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.InterceptPlatformTextInput
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalFocusManager
@@ -109,6 +111,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.drop
 import org.junit.Rule
 import org.junit.Test
@@ -1529,6 +1532,34 @@
         }
     }
 
+    // regression test for b/355900176#comment2
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun existingInputSession_doesNotSpillOver_toAnotherTextField() {
+        inputMethodInterceptor.setContent {
+            Column {
+                BasicTextField(rememberTextFieldState(), modifier = Modifier.testTag("btf1"))
+                InterceptPlatformTextInput({ _, _ -> awaitCancellation() }) {
+                    BasicTextField(rememberTextFieldState(), modifier = Modifier.testTag("btf2"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("btf1").requestFocus()
+        inputMethodInterceptor.assertSessionActive()
+
+        rule.onNodeWithTag("btf2").requestFocus()
+        inputMethodInterceptor.assertNoSessionActive()
+
+        imm.resetCalls()
+
+        // successive touches should not start the input
+        rule.onNodeWithTag("btf2").performClick()
+        inputMethodInterceptor.assertNoSessionActive()
+        // InputMethodManager should not have received a showSoftInput() call
+        rule.runOnIdle { imm.expectNoMoreCalls() }
+    }
+
     private fun requestFocus(tag: String) = rule.onNodeWithTag(tag).requestFocus()
 
     private fun assertTextSelection(expected: TextRange) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
index e7eb673..f875f45 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
@@ -184,9 +184,9 @@
                             }
                         }
                     } catch (e: PointerEventTimeoutCancellationException) {
-                        // The first tap was valid, but the second tap is a long press.
-                        // notify for the first tap
-                        onTap?.invoke(upOrCancel.position)
+                        // The first tap was valid, but the second tap is a long press - we
+                        // intentionally do not invoke onClick() for the first tap, since the 'main'
+                        // gesture here is a long press, which cancelled the double tap / tap.
 
                         // notify for the long press
                         onLongPress?.invoke(secondDown.position)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index efa21df..d7b2e5d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
@@ -97,7 +96,7 @@
      * Adds a sticky header item, which will remain pinned even when scrolling after it. The header
      * will remain pinned until the next header will take its place.
      *
-     * @sample androidx.compose.foundation.samples.StickyHeaderSample
+     * @sample androidx.compose.foundation.samples.StickyHeaderListSample
      * @param key a stable and unique key representing the item. Using the same key for multiple
      *   items in the list is not allowed. Type of the key should be saveable via Bundle on Android.
      *   If null is passed the position in the list will represent the key. When you specify the key
@@ -109,15 +108,41 @@
      *   type could be reused more efficiently. Note that null is a valid type and items of such
      *   type will be considered compatible.
      * @param content the content of the header
-     *
-     * Note: More investigations needed to make sure sticky headers API is suitable for various more
-     * generic usecases, e.g. in grids. This API is experimental until the answer is found.
      */
-    @ExperimentalFoundationApi
+    @Deprecated(
+        "Please use the overload with indexing capabilities.",
+        level = DeprecationLevel.HIDDEN,
+        replaceWith = ReplaceWith("stickyHeader(key, contentType, { _ -> content() })")
+    )
     fun stickyHeader(
         key: Any? = null,
         contentType: Any? = null,
         content: @Composable LazyItemScope.() -> Unit
+    ) = stickyHeader(key, contentType) { _ -> content() }
+
+    /**
+     * Adds a sticky header item, which will remain pinned even when scrolling after it. The header
+     * will remain pinned until the next header will take its place.
+     *
+     * @sample androidx.compose.foundation.samples.StickyHeaderListSample
+     * @sample androidx.compose.foundation.samples.StickyHeaderHeaderIndexSample
+     * @param key a stable and unique key representing the item. Using the same key for multiple
+     *   items in the list is not allowed. Type of the key should be saveable via Bundle on Android.
+     *   If null is passed the position in the list will represent the key. When you specify the key
+     *   the scroll position will be maintained based on the key, which means if you add/remove
+     *   items before the current visible item the item with the given key will be kept as the first
+     *   visible one. This can be overridden by calling 'requestScrollToItem' on the
+     *   'LazyListState'.
+     * @param contentType the type of the content of this item. The item compositions of the same
+     *   type could be reused more efficiently. Note that null is a valid type and items of such
+     *   type will be considered compatible.
+     * @param content the content of the header, the header index is provided, this is the item
+     *   position within the total set of items in this lazy list (the global index).
+     */
+    fun stickyHeader(
+        key: Any? = null,
+        contentType: Any? = null,
+        content: @Composable LazyItemScope.(Int) -> Unit
     )
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListIntervalContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListIntervalContent.kt
index ed2edfd..18bd6eb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListIntervalContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListIntervalContent.kt
@@ -65,11 +65,13 @@
     override fun stickyHeader(
         key: Any?,
         contentType: Any?,
-        content: @Composable LazyItemScope.() -> Unit
+        content: @Composable LazyItemScope.(Int) -> Unit
     ) {
         val headersIndexes = _headerIndexes ?: mutableIntListOf().also { _headerIndexes = it }
         headersIndexes.add(intervals.size)
-        item(key, contentType, content)
+        val headerIndex = intervals.size
+
+        item(key, contentType) { content(headerIndex) }
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 14de356..f03d8b3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -408,6 +408,13 @@
                 measuredItemProvider.getAndMeasure(it)
             }
 
+        val firstVisibleIndex =
+            if (noExtraItems) positionedItems.firstOrNull()?.index
+            else visibleItems.firstOrNull()?.index
+        val lastVisibleIndex =
+            if (noExtraItems) positionedItems.lastOrNull()?.index
+            else visibleItems.lastOrNull()?.index
+
         return LazyListMeasureResult(
             firstVisibleItem = firstItem,
             firstVisibleItemScrollOffset = currentFirstItemScrollOffset,
@@ -426,8 +433,8 @@
             scrollBackAmount = scrollBackAmount,
             visibleItemsInfo =
                 updatedVisibleItems(
-                    noExtraItems = noExtraItems,
-                    currentVisibleItems = visibleItems,
+                    firstVisibleIndex = firstVisibleIndex ?: 0,
+                    lastVisibleIndex = lastVisibleIndex ?: 0,
                     positionedItems = positionedItems,
                     stickingItems = stickingItems
                 ),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index e008329..04dd513 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -155,7 +155,9 @@
         LazyListScrollPosition(firstVisibleItemIndex, firstVisibleItemScrollOffset)
 
     /**
-     * The index of the first item that is visible.
+     * The index of the first item that is visible within the scrollable viewport area not including
+     * items in the content padding region. For the first visible item that includes items in the
+     * content padding please use [LazyListLayoutInfo.visibleItemsInfo].
      *
      * Note that this property is observable and if you use it in the composable function it will be
      * recomposed on every change causing potential performance issues.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index 784f123..ff524f2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.lazy.layout.LazyLayout
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.foundation.lazy.layout.StickyItemsPlacement
 import androidx.compose.foundation.lazy.layout.calculateLazyLayoutPinnedIndices
 import androidx.compose.foundation.lazy.layout.lazyLayoutBeyondBoundsModifier
 import androidx.compose.foundation.lazy.layout.lazyLayoutSemantics
@@ -42,6 +43,7 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.platform.LocalGraphicsContext
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalScrollCaptureInProgress
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.constrainHeight
@@ -83,6 +85,8 @@
 
     val coroutineScope = rememberCoroutineScope()
     val graphicsContext = LocalGraphicsContext.current
+    val stickyHeadersEnabled = !LocalScrollCaptureInProgress.current
+
     val measurePolicy =
         rememberLazyGridMeasurePolicy(
             itemProviderLambda,
@@ -94,7 +98,8 @@
             horizontalArrangement,
             verticalArrangement,
             coroutineScope,
-            graphicsContext
+            graphicsContext,
+            if (stickyHeadersEnabled) StickyItemsPlacement.StickToTopPlacement else null
         )
 
     val orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal
@@ -166,7 +171,9 @@
     /** Coroutine scope for item animations */
     coroutineScope: CoroutineScope,
     /** Used for creating graphics layers */
-    graphicsContext: GraphicsContext
+    graphicsContext: GraphicsContext,
+    /** Configures the placement of sticky items */
+    stickyItemsScrollBehavior: StickyItemsPlacement?
 ) =
     remember<LazyLayoutMeasureScope.(Constraints) -> MeasureResult>(
         state,
@@ -384,6 +391,7 @@
                     placementScopeInvalidator = state.placementScopeInvalidator,
                     prefetchInfoRetriever = prefetchInfoRetriever,
                     graphicsContext = graphicsContext,
+                    stickyItemsScrollBehavior = stickyItemsScrollBehavior,
                     layout = { width, height, placement ->
                         layout(
                             containerConstraints.constrainWidth(width + totalHorizontalPadding),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
index b814ee9..509a300 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
@@ -422,6 +422,31 @@
         contentType: (index: Int) -> Any? = { null },
         itemContent: @Composable LazyGridItemScope.(index: Int) -> Unit
     )
+
+    /**
+     * Adds a sticky header item, which will remain pinned even when scrolling after it. The header
+     * will remain pinned until the next header will take its place. Sticky Headers are full span
+     * items, that is, they will occupy [LazyGridItemSpanScope.maxLineSpan].
+     *
+     * @sample androidx.compose.foundation.samples.StickyHeaderGridSample
+     * @param key a stable and unique key representing the item. Using the same key for multiple
+     *   items in the list is not allowed. Type of the key should be saveable via Bundle on Android.
+     *   If null is passed the position in the list will represent the key. When you specify the key
+     *   the scroll position will be maintained based on the key, which means if you add/remove
+     *   items before the current visible item the item with the given key will be kept as the first
+     *   visible one. This can be overridden by calling 'requestScrollToItem' on the
+     *   'LazyGridState'.
+     * @param contentType the type of the content of this item. The item compositions of the same
+     *   type could be reused more efficiently. Note that null is a valid type and items of such
+     *   type will be considered compatible.
+     * @param content the content of the header. The header index is provided, this is the item
+     *   position within the total set of items in this lazy list (the global index).
+     */
+    fun stickyHeader(
+        key: Any? = null,
+        contentType: Any? = null,
+        content: @Composable LazyGridItemScope.(Int) -> Unit
+    )
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridIntervalContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridIntervalContent.kt
index 39c3218..f4ed2f2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridIntervalContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridIntervalContent.kt
@@ -16,6 +16,10 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.collection.IntList
+import androidx.collection.MutableIntList
+import androidx.collection.emptyIntList
+import androidx.collection.mutableIntListOf
 import androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent
 import androidx.compose.foundation.lazy.layout.MutableIntervalList
 import androidx.compose.runtime.Composable
@@ -28,6 +32,11 @@
 
     internal var hasCustomSpans = false
 
+    private var _headerIndexes: MutableIntList? = null
+
+    val headerIndexes: IntList
+        get() = _headerIndexes ?: emptyIntList()
+
     init {
         apply(content)
     }
@@ -69,6 +78,17 @@
         if (span != null) hasCustomSpans = true
     }
 
+    override fun stickyHeader(
+        key: Any?,
+        contentType: Any?,
+        content: @Composable LazyGridItemScope.(Int) -> Unit
+    ) {
+        val headersIndexes = _headerIndexes ?: mutableIntListOf().also { _headerIndexes = it }
+        val headerIndex = intervals.size
+        headersIndexes.add(headerIndex)
+        item(key, { GridItemSpan(maxLineSpan) }, contentType) { content.invoke(this, headerIndex) }
+    }
+
     private companion object {
         val DefaultSpan: LazyGridItemSpanScope.(Int) -> GridItemSpan = { GridItemSpan(1) }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
index 07116974..497fc52 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.collection.IntList
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
@@ -27,10 +28,12 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 
+@Suppress("PrimitiveInCollection")
 @OptIn(ExperimentalFoundationApi::class)
 internal interface LazyGridItemProvider : LazyLayoutItemProvider {
     val keyIndexMap: LazyLayoutKeyIndexMap
     val spanLayoutProvider: LazyGridSpanLayoutProvider
+    val headerIndexes: IntList
 }
 
 @Composable
@@ -72,6 +75,9 @@
 
     override fun getContentType(index: Int): Any? = intervalContent.getContentType(index)
 
+    override val headerIndexes: IntList
+        get() = intervalContent.headerIndexes
+
     @Composable
     override fun Item(index: Int, key: Any) {
         LazyLayoutPinnableItem(key, index, state.pinnedItems) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index 549b2a7..22d00c9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -23,6 +23,9 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
+import androidx.compose.foundation.lazy.layout.StickyItemsPlacement
+import androidx.compose.foundation.lazy.layout.applyStickyItems
+import androidx.compose.foundation.lazy.layout.updatedVisibleItems
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
@@ -32,7 +35,6 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
-import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.fastRoundToInt
@@ -70,6 +72,7 @@
     placementScopeInvalidator: ObservableScopeInvalidator,
     graphicsContext: GraphicsContext,
     prefetchInfoRetriever: (line: Int) -> List<Pair<Int, Constraints>>,
+    stickyItemsScrollBehavior: StickyItemsPlacement?,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyGridMeasureResult {
     requirePrecondition(beforeContentPadding >= 0) { "negative beforeContentPadding" }
@@ -360,6 +363,26 @@
             }
         }
 
+        // apply sticky items logic.
+        val stickingItems =
+            stickyItemsScrollBehavior.applyStickyItems(
+                positionedItems,
+                measuredItemProvider.headerIndices,
+                beforeContentPadding,
+                afterContentPadding,
+                layoutWidth,
+                layoutHeight
+            ) {
+                val span = measuredLineProvider.spanOf(it)
+                val childConstraints = measuredLineProvider.childConstraints(0, span)
+                measuredItemProvider.getAndMeasure(
+                    index = it,
+                    constraints = childConstraints,
+                    lane = 0,
+                    span = span
+                )
+            }
+
         return LazyGridMeasureResult(
             firstVisibleLine = firstLine,
             firstVisibleLineScrollOffset = currentFirstLineScrollOffset,
@@ -368,17 +391,14 @@
             measureResult =
                 layout(layoutWidth, layoutHeight) {
                     positionedItems.fastForEach { it.place(this) }
+                    stickingItems.fastForEach { it.place(this) }
                     // we attach it during the placement so LazyGridState can trigger re-placement
                     placementScopeInvalidator.attachToScope()
                 },
             viewportStartOffset = -beforeContentPadding,
             viewportEndOffset = mainAxisAvailableSize + afterContentPadding,
             visibleItemsInfo =
-                if (extraItemsBefore.isEmpty() && extraItemsAfter.isEmpty()) {
-                    positionedItems
-                } else {
-                    positionedItems.fastFilter { it.index in firstItemIndex..lastItemIndex }
-                },
+                updatedVisibleItems(firstItemIndex, lastItemIndex, positionedItems, stickingItems),
             totalItemsCount = itemsCount,
             reverseLayout = reverseLayout,
             orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
index 5c33a25..d16d0c0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.collection.IntList
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
@@ -86,6 +87,9 @@
     val keyIndexMap: LazyLayoutKeyIndexMap
         get() = itemProvider.keyIndexMap
 
+    val headerIndices: IntList
+        get() = itemProvider.headerIndexes
+
     abstract fun createItem(
         index: Int,
         key: Any,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
index b9dcbb46..23a9c30 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
@@ -26,6 +26,7 @@
 
     /** Caches the bucket info on lines 0, [bucketSize], 2 * [bucketSize], etc. */
     private val buckets = ArrayList<Bucket>().apply { add(Bucket(0)) }
+
     /**
      * The interval at each we will store the starting element of lines. These will be then used to
      * calculate the layout of arbitrary lines, by starting from the closest known "bucket start".
@@ -37,20 +38,25 @@
 
     /** Caches the last calculated line index, useful when scrolling in main axis direction. */
     private var lastLineIndex = 0
+
     /** Caches the starting item index on [lastLineIndex]. */
     private var lastLineStartItemIndex = 0
+
     /** Caches the span of [lastLineStartItemIndex], if this was already calculated. */
     private var lastLineStartKnownSpan = 0
+
     /**
      * Caches a calculated bucket, this is useful when scrolling in reverse main axis direction. We
      * cannot only keep the last element, as we would not know previous max span.
      */
     private var cachedBucketIndex = -1
+
     /**
      * Caches layout of [cachedBucketIndex], this is useful when scrolling in reverse main axis
      * direction. We cannot only keep the last element, as we would not know previous max span.
      */
     private val cachedBucket = mutableListOf<Int>()
+
     /** List of 1x1 spans if we do not have custom spans. */
     private var previousDefaultSpans = emptyList<GridItemSpan>()
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index fc0832a..08e88a4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -141,7 +141,9 @@
         LazyGridScrollPosition(firstVisibleItemIndex, firstVisibleItemScrollOffset)
 
     /**
-     * The index of the first item that is visible.
+     * The index of the first item that is visible within the scrollable viewport area, this means,
+     * not including items in the content padding region. For the first visible item that includes
+     * items in the content padding please use [LazyGridLayoutInfo.visibleItemsInfo].
      *
      * Note that this property is observable and if you use it in the composable function it will be
      * recomposed on every change causing potential performance issues.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
index 12a9249..d91adff 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
@@ -88,8 +88,8 @@
 
         val hasAnimations = positionedItems.fastAny { it.hasAnimations }
         if (!hasAnimations && keyToItemInfoMap.isEmpty()) {
-            // no animations specified - no work needed
-            reset()
+            // no animations specified - no work needed - clear animation info
+            releaseAnimations()
             return
         }
 
@@ -354,14 +354,18 @@
      * example when we snap to a new position.
      */
     fun reset() {
+        releaseAnimations()
+        keyIndexMap = null
+        firstVisibleIndex = -1
+    }
+
+    private fun releaseAnimations() {
         if (keyToItemInfoMap.isNotEmpty()) {
             keyToItemInfoMap.forEachValue {
                 it.animations.forEach { animation -> animation?.release() }
             }
             keyToItemInfoMap.clear()
         }
-        keyIndexMap = LazyLayoutKeyIndexMap.Empty
-        firstVisibleIndex = -1
     }
 
     private fun initializeAnimation(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasuredItem.kt
index 213d7a2..36e9bef 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasuredItem.kt
@@ -43,16 +43,12 @@
 }
 
 internal fun <T : LazyLayoutMeasuredItem> updatedVisibleItems(
-    noExtraItems: Boolean,
-    currentVisibleItems: List<T>,
+    firstVisibleIndex: Int,
+    lastVisibleIndex: Int,
     positionedItems: List<T>,
-    stickingItems: List<T>
+    stickingItems: List<T>,
 ): List<T> {
-    if (positionedItems.isEmpty() || currentVisibleItems.isEmpty()) return emptyList()
-    val firstVisibleIndex =
-        if (noExtraItems) positionedItems.first().index else currentVisibleItems.first().index
-    val lastVisibleIndex =
-        if (noExtraItems) positionedItems.last().index else currentVisibleItems.last().index
+    if (positionedItems.isEmpty()) return emptyList()
 
     val finalVisibleItems = stickingItems.toMutableList()
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index bba2028..f1b227a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -41,6 +41,7 @@
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.FullSpan
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
@@ -82,6 +83,7 @@
  * most cases, it should be created via [rememberLazyStaggeredGridState].
  */
 @OptIn(ExperimentalFoundationApi::class)
+@Stable
 class LazyStaggeredGridState
 internal constructor(
     initialFirstVisibleItems: IntArray,
@@ -102,7 +104,9 @@
     )
 
     /**
-     * Index of the first visible item across all staggered grid lanes.
+     * Index of the first visible item across all staggered grid lanes. This does not include items
+     * in the content padding region. For the first visible item that includes items in the content
+     * padding please use [LazyStaggeredGridLayoutInfo.visibleItemsInfo].
      *
      * This property is observable and when use it in composable function it will be recomposed on
      * each scroll, potentially causing performance issues.
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 64341fe..796b66e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -507,7 +507,7 @@
             }
 
         Canvas(Modifier.fillMaxSize().then(dismissModifier)) {
-            drawRect(color = color, alpha = alpha)
+            drawRect(color = color, alpha = alpha.coerceIn(0f, 1f))
         }
     }
 }
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 3863b9c..c69428b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -35,7 +35,8 @@
   }
 
   public final class ListDetailPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   public final class ListDetailPaneScaffoldRole {
@@ -59,6 +60,39 @@
     property public final String Hidden;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor {
+  }
+
+  public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Offset(float offset);
+    method public float getOffset();
+    property public final float offset;
+  }
+
+  public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion);
+    method public float getProportion();
+    property public final float proportion;
+  }
+
+  public final class PaneExpansionDragHandleKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState {
+    method public void clear();
+    method public void dispatchRawDelta(float delta);
+    method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isUnspecified();
+    method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion);
+    method public void setFirstPaneWidth(int firstPaneWidth);
+    field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion;
+    field public static final int Unspecified = -1; // 0xffffffff
+  }
+
+  public static final class PaneExpansionState.Companion {
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey {
     field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion;
   }
@@ -73,6 +107,11 @@
     property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey;
   }
 
+  public final class PaneExpansionStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+  }
+
   public final class PaneKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
@@ -115,7 +154,8 @@
   }
 
   public final class SupportingPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
@@ -164,6 +204,19 @@
     property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldState {
+    ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
+    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public final float progressFraction;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider {
     ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
     method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 3863b9c..c69428b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -35,7 +35,8 @@
   }
 
   public final class ListDetailPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   public final class ListDetailPaneScaffoldRole {
@@ -59,6 +60,39 @@
     property public final String Hidden;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor {
+  }
+
+  public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Offset(float offset);
+    method public float getOffset();
+    property public final float offset;
+  }
+
+  public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion);
+    method public float getProportion();
+    property public final float proportion;
+  }
+
+  public final class PaneExpansionDragHandleKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState {
+    method public void clear();
+    method public void dispatchRawDelta(float delta);
+    method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isUnspecified();
+    method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion);
+    method public void setFirstPaneWidth(int firstPaneWidth);
+    field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion;
+    field public static final int Unspecified = -1; // 0xffffffff
+  }
+
+  public static final class PaneExpansionState.Companion {
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey {
     field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion;
   }
@@ -73,6 +107,11 @@
     property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey;
   }
 
+  public final class PaneExpansionStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+  }
+
   public final class PaneKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
@@ -115,7 +154,8 @@
   }
 
   public final class SupportingPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
@@ -164,6 +204,19 @@
     property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldState {
+    ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
+    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public final float progressFraction;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider {
     ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
     method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index cf597c7..9400e45 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -42,9 +42,9 @@
             dependencies {
                 implementation(libs.kotlinStdlib)
                 api(project(":compose:material3:adaptive:adaptive"))
-                api("androidx.compose.animation:animation-core:1.7.0-beta06")
-                api("androidx.compose.ui:ui:1.7.0-beta06")
-                implementation("androidx.compose.animation:animation:1.7.0-beta06")
+                api("androidx.compose.animation:animation-core:1.7.0-rc01")
+                api("androidx.compose.ui:ui:1.7.0-rc01")
+                implementation("androidx.compose.animation:animation:1.7.0-rc01")
                 implementation("androidx.compose.foundation:foundation:1.6.5")
                 implementation("androidx.compose.foundation:foundation-layout:1.6.5")
                 implementation("androidx.compose.ui:ui-geometry:1.6.5")
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
index d8bfa84..36723c0 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
@@ -116,8 +116,9 @@
     fun threePaneScaffold_paneExpansion_fixedFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 412.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -134,7 +135,7 @@
     fun threePaneScaffold_paneExpansion_zeroFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth = 0
+            mockPaneExpansionState.setFirstPaneWidth(0)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -151,8 +152,9 @@
     fun threePaneScaffold_paneExpansion_overflowFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 1024.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -169,7 +171,7 @@
     fun threePaneScaffold_paneExpansion_fixedFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.5f
+            mockPaneExpansionState.setFirstPaneProportion(0.5f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -186,7 +188,7 @@
     fun threePaneScaffold_paneExpansion_zeroFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0f
+            mockPaneExpansionState.setFirstPaneProportion(0f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -203,7 +205,7 @@
     fun threePaneScaffold_paneExpansion_smallFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.05f
+            mockPaneExpansionState.setFirstPaneProportion(0.05f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -220,7 +222,7 @@
     fun threePaneScaffold_paneExpansion_largeFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.95f
+            mockPaneExpansionState.setFirstPaneProportion(0.95f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -237,7 +239,7 @@
     fun threePaneScaffold_paneExpansion_fullFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 1.0f
+            mockPaneExpansionState.setFirstPaneProportion(1f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -254,8 +256,9 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_fixedFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 412.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
@@ -272,7 +275,7 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_zeroFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth = 0
+            mockPaneExpansionState.setFirstPaneWidth(0)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
@@ -289,8 +292,9 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_overflowFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 1024.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
index 860b4dc..957275d 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -270,9 +270,9 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockPaneExpansionAnchors =
     listOf(
-        PaneExpansionAnchor(percentage = 0),
-        PaneExpansionAnchor(startOffset = MockPaneExpansionMiddleAnchor),
-        PaneExpansionAnchor(percentage = 100),
+        PaneExpansionAnchor.Proportion(0f),
+        PaneExpansionAnchor.Offset(MockPaneExpansionMiddleAnchor),
+        PaneExpansionAnchor.Proportion(1f),
     )
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
index 0294dba..0403a04 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -22,8 +22,12 @@
 import androidx.compose.ui.Modifier
 
 /**
- * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
- * panes in a canonical list-detail layout.
+ * An opinionated implementation of [ThreePaneScaffold] following Material guidelines that displays
+ * the provided three panes in a canonical [list-detail layout](
+ * https://m3.material.io/foundations/layout/canonical-layouts/list-detail).
+ *
+ * This overload takes a [ThreePaneScaffoldValue] describing the adapted value of each pane within
+ * the scaffold.
  *
  * See usage samples at:
  *
@@ -43,6 +47,13 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
  *   besides the list and the detail panes, for example, a task list or a mini-calendar view of a
  *   mail app. See [ListDetailPaneScaffoldRole.Extra].
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -53,6 +64,8 @@
     detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
 ) {
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
@@ -61,6 +74,51 @@
         paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
         secondaryPane = listPane,
         tertiaryPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
+        primaryPane = detailPane
+    )
+}
+
+/**
+ * An opinionated implementation of [ThreePaneScaffold] following Material guidelines that displays
+ * the provided three panes in a canonical [list-detail layout](
+ * https://m3.material.io/foundations/layout/canonical-layouts/list-detail).
+ *
+ * This overload takes a [ThreePaneScaffoldState] describing the current [ThreePaneScaffoldValue]
+ * and any pane transitions or animations in progress.
+ *
+ * @param directive The top-level directives about how the scaffold should arrange its panes.
+ * @param scaffoldState The current state of the scaffold, containing information about the adapted
+ *   value of each pane of the scaffold and the transitions/animations in progress.
+ * @param listPane the list pane of the scaffold, which is supposed to hold a list of item summaries
+ *   that can be selected from, for example, the inbox mail list of a mail app. See
+ *   [ListDetailPaneScaffoldRole.List].
+ * @param detailPane the detail pane of the scaffold, which is supposed to hold the detailed info of
+ *   a selected item, for example, the mail content currently being viewed. See
+ *   [ListDetailPaneScaffoldRole.Detail].
+ * @param modifier [Modifier] of the scaffold layout.
+ * @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
+ *   besides the list and the detail panes, for example, a task list or a mini-calendar view of a
+ *   mail app. See [ListDetailPaneScaffoldRole.Extra].
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun ListDetailPaneScaffold(
+    directive: PaneScaffoldDirective,
+    scaffoldState: ThreePaneScaffoldState,
+    listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+) {
+    ThreePaneScaffold(
+        modifier = modifier.fillMaxSize(),
+        scaffoldDirective = directive,
+        scaffoldState = scaffoldState,
+        paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
+        secondaryPane = listPane,
+        tertiaryPane = extraPane,
         primaryPane = detailPane
     )
 }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
index 0a83188..8398174 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
@@ -30,14 +30,20 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.unit.dp
 
+/**
+ * A default, basic non-customizable implementation of pane expansion drag handle. Note that this
+ * implementation will be deprecated in favor of the corresponding Material3 implementation when
+ * it's available.
+ */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-// TODO(b/327637983): Implement this as a customizable component.
-internal fun PaneExpansionDragHandle(
+// TODO(b/327637983): Implement this as a customizable component as a Material3 component.
+fun PaneExpansionDragHandle(
     state: PaneExpansionState,
     color: Color,
     modifier: Modifier = Modifier,
 ) {
+    // TODO (conradchen): support drag handle motion during scaffold and expansion state change
     Box(
         modifier = modifier.paneExpansionDragHandle(state).size(24.dp, 48.dp),
         contentAlignment = Alignment.Center
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
index 7a0d154..df95da6 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
@@ -16,18 +16,17 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.annotation.IntRange
+import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
 import androidx.collection.IntList
 import androidx.collection.MutableIntList
-import androidx.collection.emptyIntList
 import androidx.compose.animation.core.animate
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.gestures.DragScope
 import androidx.compose.foundation.gestures.DraggableState
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.UnspecifiedWidth
+import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.Unspecified
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -40,8 +39,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.isSpecified
 import kotlin.math.abs
+import kotlin.math.roundToInt
 import kotlinx.coroutines.coroutineScope
 
 /**
@@ -96,7 +95,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-internal fun rememberPaneExpansionState(
+fun rememberPaneExpansionState(
     keyProvider: PaneExpansionStateKeyProvider,
     anchors: List<PaneExpansionAnchor> = emptyList()
 ): PaneExpansionState = rememberPaneExpansionState(keyProvider.paneExpansionStateKey, anchors)
@@ -112,7 +111,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-internal fun rememberPaneExpansionState(
+fun rememberPaneExpansionState(
     key: PaneExpansionStateKey = PaneExpansionStateKey.Default,
     anchors: List<PaneExpansionAnchor> = emptyList()
 ): PaneExpansionState {
@@ -129,32 +128,35 @@
     }
 }
 
+/**
+ * This class manages the pane expansion state for pane scaffolds. By providing and modifying an
+ * instance of this class, you can specify the expanded panes' expansion width or percentage when
+ * pane scaffold is displaying a dual-pane layout.
+ *
+ * This class also serves as the [DraggableState] of pane expansion handle. When a handle
+ * implementation is provided to the associated pane scaffold, the scaffold will use
+ * [PaneExpansionState] to store and manage dragging and anchoring of the handle, and thus the pane
+ * expansion state.
+ */
 @ExperimentalMaterial3AdaptiveApi
 @Stable
-internal class PaneExpansionState
+class PaneExpansionState
 internal constructor(
     // TODO(conradchen): Handle state change during dragging and settling
     data: PaneExpansionStateData = PaneExpansionStateData(),
-    internal var anchors: List<PaneExpansionAnchor> = emptyList()
+    anchors: List<PaneExpansionAnchor> = emptyList()
 ) : DraggableState {
 
-    var firstPaneWidth: Int
-        set(value) {
-            data.firstPanePercentageState = Float.NaN
-            data.currentDraggingOffsetState = UnspecifiedWidth
-            val coercedValue = value.coerceIn(0, maxExpansionWidth)
-            data.firstPaneWidthState = coercedValue
-        }
-        get() = data.firstPaneWidthState
+    internal val firstPaneWidth
+        get() =
+            if (maxExpansionWidth == Unspecified || data.firstPaneWidthState == Unspecified) {
+                Unspecified
+            } else {
+                data.firstPaneWidthState.coerceIn(0, maxExpansionWidth)
+            }
 
-    var firstPanePercentage: Float
-        set(value) {
-            require(value in 0f..1f) { "Percentage value needs to be in [0, 1]" }
-            data.firstPaneWidthState = UnspecifiedWidth
-            data.currentDraggingOffsetState = UnspecifiedWidth
-            data.firstPanePercentageState = value
-        }
-        get() = data.firstPanePercentageState
+    internal val firstPaneProportion: Float
+        get() = data.firstPaneProportionState
 
     internal var currentDraggingOffset
         get() = data.currentDraggingOffsetState
@@ -179,16 +181,18 @@
         get() = isDragging || isSettling
 
     @VisibleForTesting
-    internal var maxExpansionWidth = 0
+    internal var maxExpansionWidth by mutableIntStateOf(Unspecified)
         private set
 
     // Use this field to store the dragging offset decided by measuring instead of dragging to
     // prevent redundant re-composition.
     @VisibleForTesting
-    internal var currentMeasuredDraggingOffset = UnspecifiedWidth
+    internal var currentMeasuredDraggingOffset = Unspecified
         private set
 
-    private var anchorPositions: IntList = emptyIntList()
+    internal var anchors: List<PaneExpansionAnchor> by mutableStateOf(anchors)
+
+    private lateinit var measuredDensity: Density
 
     private val dragScope: DragScope =
         object : DragScope {
@@ -197,13 +201,14 @@
 
     private val dragMutex = MutatorMutex()
 
+    /** Returns `true` if none of [firstPaneWidth] or [firstPaneProportion] has been set. */
     fun isUnspecified(): Boolean =
-        firstPaneWidth == UnspecifiedWidth &&
-            firstPanePercentage.isNaN() &&
-            currentDraggingOffset == UnspecifiedWidth
+        firstPaneWidth == Unspecified &&
+            firstPaneProportion.isNaN() &&
+            currentDraggingOffset == Unspecified
 
     override fun dispatchRawDelta(delta: Float) {
-        if (currentMeasuredDraggingOffset == UnspecifiedWidth) {
+        if (currentMeasuredDraggingOffset == Unspecified) {
             return
         }
         currentDraggingOffset = (currentMeasuredDraggingOffset + delta).toInt()
@@ -216,11 +221,45 @@
             isDragging = false
         }
 
-    /** Clears any existing expansion state. */
+    /**
+     * Set the width of the first expanded pane in the layout. When the set value gets applied, it
+     * will be coerced within the range of `[0, the full displayable width of the layout]`.
+     *
+     * Note that setting this value will reset the first pane proportion previously set via
+     * [setFirstPaneProportion] or the current dragging result if there's any. Also if user drags
+     * the pane after setting the first pane width, the user dragging result will take the priority
+     * over this set value when rendering panes, but the set value will be saved.
+     */
+    fun setFirstPaneWidth(firstPaneWidth: Int) {
+        data.firstPaneProportionState = Float.NaN
+        data.currentDraggingOffsetState = Unspecified
+        data.firstPaneWidthState = firstPaneWidth
+    }
+
+    /**
+     * Set the proportion of the first expanded pane in the layout. The set value needs to be within
+     * the range of `[0f, 1f]`, otherwise the setter throws.
+     *
+     * Note that setting this value will reset the first pane width previously set via
+     * [setFirstPaneWidth] or the current dragging result if there's any. Also if user drags the
+     * pane after setting the first pane proportion, the user dragging result will take the priority
+     * over this set value when rendering panes, but the set value will be saved.
+     */
+    fun setFirstPaneProportion(@FloatRange(0.0, 1.0) firstPaneProportion: Float) {
+        require(firstPaneProportion in 0f..1f) { "Proportion value needs to be in [0f, 1f]" }
+        data.firstPaneWidthState = Unspecified
+        data.currentDraggingOffsetState = Unspecified
+        data.firstPaneProportionState = firstPaneProportion
+    }
+
+    /**
+     * Clears any previously set [firstPaneWidth] or [firstPaneProportion], as well as the user
+     * dragging result.
+     */
     fun clear() {
-        data.firstPaneWidthState = UnspecifiedWidth
-        data.firstPanePercentageState = Float.NaN
-        data.currentDraggingOffsetState = UnspecifiedWidth
+        data.firstPaneWidthState = Unspecified
+        data.firstPaneProportionState = Float.NaN
+        data.currentDraggingOffsetState = Unspecified
     }
 
     internal fun onMeasured(measuredWidth: Int, density: Density) {
@@ -228,10 +267,11 @@
             return
         }
         maxExpansionWidth = measuredWidth
-        if (firstPaneWidth != UnspecifiedWidth) {
-            firstPaneWidth = firstPaneWidth
+        measuredDensity = density
+        // To re-coerce the value
+        if (currentDraggingOffset != Unspecified) {
+            currentDraggingOffset = currentDraggingOffset
         }
-        anchorPositions = anchors.toPositions(measuredWidth, density)
     }
 
     internal fun onExpansionOffsetMeasured(measuredOffset: Int) {
@@ -239,7 +279,7 @@
     }
 
     internal suspend fun settleToAnchorIfNeeded(velocity: Float) {
-        val currentAnchorPositions = anchorPositions
+        val currentAnchorPositions = anchors.toPositions(maxExpansionWidth, measuredDensity)
         if (currentAnchorPositions.isEmpty()) {
             return
         }
@@ -283,41 +323,72 @@
         )
 
     companion object {
-        const val UnspecifiedWidth = -1
+        /** The constant value used to denote the pane expansion is not specified. */
+        const val Unspecified = -1
+
         private const val AnchoringVelocityThreshold = 200F
     }
 }
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal class PaneExpansionStateData {
-    var firstPaneWidthState by mutableIntStateOf(UnspecifiedWidth)
-    var firstPanePercentageState by mutableFloatStateOf(Float.NaN)
-    var currentDraggingOffsetState by mutableIntStateOf(UnspecifiedWidth)
+    var firstPaneWidthState by mutableIntStateOf(Unspecified)
+    var firstPaneProportionState by mutableFloatStateOf(Float.NaN)
+    var currentDraggingOffsetState by mutableIntStateOf(Unspecified)
 }
 
+/**
+ * The implementations of this interface represent different types of anchors of pane expansion
+ * dragging. Setting up anchors when create [PaneExpansionState] will force user dragging to snap to
+ * the set anchors after user releases the drag.
+ */
 @ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class PaneExpansionAnchor
-private constructor(
-    val percentage: Int,
-    val startOffset: Dp // TODO(conradchen): confirm RTL support
-) {
-    constructor(@IntRange(0, 100) percentage: Int) : this(percentage, Dp.Unspecified)
+sealed class PaneExpansionAnchor private constructor() {
+    internal abstract fun positionIn(totalSizePx: Int, density: Density): Int
 
-    constructor(startOffset: Dp) : this(Int.MIN_VALUE, startOffset)
+    /**
+     * [PaneExpansionAnchor] implementation that specifies the anchor position in the proportion of
+     * the total size of the layout at the start side of the anchor.
+     *
+     * @property proportion the proportion of the layout at the start side of the anchor. layout.
+     */
+    class Proportion(@FloatRange(0.0, 1.0) val proportion: Float) : PaneExpansionAnchor() {
+        override fun positionIn(totalSizePx: Int, density: Density) =
+            (totalSizePx * proportion).roundToInt().coerceIn(0, totalSizePx)
 
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is PaneExpansionAnchor) return false
-        if (percentage != other.percentage) return false
-        if (startOffset != other.startOffset) return false
-        return true
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Proportion) return false
+            return proportion == other.proportion
+        }
+
+        override fun hashCode(): Int {
+            return proportion.hashCode()
+        }
     }
 
-    override fun hashCode(): Int {
-        var result = percentage
-        result = 31 * result + startOffset.hashCode()
-        return result
+    /**
+     * [PaneExpansionAnchor] implementation that specifies the anchor position in the offset in
+     * [Dp]. If a positive value is provided, the offset will be treated as a start offset, on the
+     * other hand, if a negative value is provided, the absolute value of the provided offset will
+     * be used as an end offset. For example, if -150.dp is provided, the resulted anchor will be at
+     * the position that is 150dp away from the end side of the associated layout.
+     *
+     * @property offset the offset of the anchor in [Dp].
+     */
+    class Offset(val offset: Dp) : PaneExpansionAnchor() {
+        override fun positionIn(totalSizePx: Int, density: Density) =
+            with(density) { offset.toPx() }.toInt().let { if (it < 0) totalSizePx + it else it }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Offset) return false
+            return offset == other.offset
+        }
+
+        override fun hashCode(): Int {
+            return offset.hashCode()
+        }
     }
 }
 
@@ -328,19 +399,7 @@
 ): IntList {
     val anchors = MutableIntList(size)
     @Suppress("ListIterator") // Not necessarily a random-accessible list
-    forEach { anchor ->
-        if (anchor.startOffset.isSpecified) {
-            val position =
-                with(density) { anchor.startOffset.toPx() }
-                    .toInt()
-                    .let { if (it < 0) maxExpansionWidth + it else it }
-            if (position in 0..maxExpansionWidth) {
-                anchors.add(position)
-            }
-        } else {
-            anchors.add(maxExpansionWidth * anchor.percentage / 100)
-        }
-    }
+    forEach { anchor -> anchors.add(anchor.positionIn(maxExpansionWidth, density)) }
     anchors.sort()
     return anchors
 }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
index 2c5acfa..40d8c68 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -22,8 +22,12 @@
 import androidx.compose.ui.Modifier
 
 /**
- * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
- * panes in a canonical supporting-pane layout.
+ * An opinionated implementation of [ThreePaneScaffold] following Material guidelines that displays
+ * the provided three panes in a canonical [supporting pane layout](
+ * https://m3.material.io/foundations/layout/canonical-layouts/supporting-pane).
+ *
+ * This overload takes a [ThreePaneScaffoldValue] describing the adapted value of each pane within
+ * the scaffold.
  *
  * @param directive The top-level directives about how the scaffold should arrange its panes.
  * @param value The current adapted value of the scaffold, which indicates how each pane of the
@@ -37,12 +41,65 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
  *   besides the main and the supporting panes, for example, a styling panel in a doc app. See
  *   [SupportingPaneScaffoldRole.Extra].
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun SupportingPaneScaffold(
+    directive: PaneScaffoldDirective,
+    value: ThreePaneScaffoldValue,
+    mainPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
+) {
+    ThreePaneScaffold(
+        modifier = modifier.fillMaxSize(),
+        scaffoldDirective = directive,
+        scaffoldValue = value,
+        paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
+        secondaryPane = supportingPane,
+        tertiaryPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
+        primaryPane = mainPane
+    )
+}
+
+/**
+ * An opinionated implementation of [ThreePaneScaffold] following Material guidelines that displays
+ * the provided three panes in a canonical [supporting pane layout](
+ * https://m3.material.io/foundations/layout/canonical-layouts/supporting-pane).
+ *
+ * This overload takes a [ThreePaneScaffoldState] describing the current [ThreePaneScaffoldValue]
+ * and any pane transitions or animations in progress.
+ *
+ * @param directive The top-level directives about how the scaffold should arrange its panes.
+ * @param scaffoldState The current state of the scaffold, containing information about the adapted
+ *   value of each pane of the scaffold and the transitions/animations in progress.
+ * @param mainPane the main pane of the scaffold, which is supposed to hold the major content of an
+ *   app, for example, the editing screen of a doc app. See [SupportingPaneScaffoldRole.Main].
+ * @param supportingPane the supporting pane of the scaffold, which is supposed to hold the support
+ *   content of an app, for example, the comment list of a doc app. See
+ *   [SupportingPaneScaffoldRole.Supporting].
+ * @param modifier [Modifier] of the scaffold layout.
+ * @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
+ *   besides the main and the supporting panes, for example, a styling panel in a doc app. See
+ *   [SupportingPaneScaffoldRole.Extra].
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
 fun SupportingPaneScaffold(
     directive: PaneScaffoldDirective,
-    value: ThreePaneScaffoldValue,
+    scaffoldState: ThreePaneScaffoldState,
     mainPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
@@ -51,7 +108,7 @@
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
         scaffoldDirective = directive,
-        scaffoldValue = value,
+        scaffoldState = scaffoldState,
         paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
         secondaryPane = supportingPane,
         tertiaryPane = extraPane,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index 63966f8..d85c569 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -19,9 +19,7 @@
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.SeekableTransitionState
 import androidx.compose.animation.core.Transition
-import androidx.compose.animation.core.rememberTransition
 import androidx.compose.animation.core.snap
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.runtime.Composable
@@ -91,7 +89,7 @@
     paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
     primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
 ) {
-    val scaffoldState = remember { SeekableTransitionState(scaffoldValue) }
+    val scaffoldState = remember { ThreePaneScaffoldState(scaffoldValue) }
     LaunchedEffect(key1 = scaffoldValue) { scaffoldState.animateTo(scaffoldValue) }
     ThreePaneScaffold(
         modifier = modifier,
@@ -111,7 +109,7 @@
 internal fun ThreePaneScaffold(
     modifier: Modifier,
     scaffoldDirective: PaneScaffoldDirective,
-    scaffoldState: SeekableTransitionState<ThreePaneScaffoldValue>,
+    scaffoldState: ThreePaneScaffoldState,
     paneOrder: ThreePaneScaffoldHorizontalOrder,
     secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
@@ -137,7 +135,7 @@
             )
         }
 
-    val currentTransition = rememberTransition(scaffoldState)
+    val currentTransition = scaffoldState.rememberTransition()
 
     LookaheadScope {
         // Create PaneWrappers for each of the panes and map the transitions according to each pane
@@ -316,9 +314,7 @@
 
             if (!paneExpansionState.isUnspecified() && visiblePanes.size == 2) {
                 // Pane expansion should override everything
-                if (
-                    paneExpansionState.currentDraggingOffset != PaneExpansionState.UnspecifiedWidth
-                ) {
+                if (paneExpansionState.currentDraggingOffset != PaneExpansionState.Unspecified) {
                     // Respect the user dragging result if there's any
                     val halfSpacerSize = verticalSpacerSize / 2
                     if (paneExpansionState.currentDraggingOffset <= halfSpacerSize) {
@@ -368,7 +364,7 @@
                     val availableWidth = constraints.maxWidth
                     if (
                         paneExpansionState.firstPaneWidth == 0 ||
-                            paneExpansionState.firstPanePercentage == 0f
+                            paneExpansionState.firstPaneProportion == 0f
                     ) {
                         measureAndPlacePaneWithLocalBounds(
                             outerBounds,
@@ -377,7 +373,7 @@
                         )
                     } else if (
                         paneExpansionState.firstPaneWidth >= availableWidth - verticalSpacerSize ||
-                            paneExpansionState.firstPanePercentage >= 1f
+                            paneExpansionState.firstPaneProportion >= 1f
                     ) {
                         measureAndPlacePaneWithLocalBounds(
                             outerBounds,
@@ -387,12 +383,11 @@
                     } else {
                         val firstPaneWidth =
                             if (
-                                paneExpansionState.firstPaneWidth !=
-                                    PaneExpansionState.UnspecifiedWidth
+                                paneExpansionState.firstPaneWidth != PaneExpansionState.Unspecified
                             ) {
                                 paneExpansionState.firstPaneWidth
                             } else {
-                                (paneExpansionState.firstPanePercentage *
+                                (paneExpansionState.firstPaneProportion *
                                         (availableWidth - verticalSpacerSize))
                                     .toInt()
                             }
@@ -507,7 +502,7 @@
                     if (
                         !paneExpansionState.isDraggingOrSettling ||
                             paneExpansionState.currentDraggingOffset ==
-                                PaneExpansionState.UnspecifiedWidth
+                                PaneExpansionState.Unspecified
                     ) {
                         val spacerMiddleOffset =
                             getSpacerMiddleOffsetX(visiblePanes[0], visiblePanes[1])
@@ -526,7 +521,7 @@
                     handleOffsetX
                 )
             } else if (!isLookingAhead) {
-                paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.UnspecifiedWidth)
+                paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.Unspecified)
             }
 
             // Place the hidden panes to ensure a proper motion at the AnimatedVisibility,
@@ -705,7 +700,7 @@
         maxHandleWidth: Int,
         offsetX: Int
     ) {
-        if (offsetX == PaneExpansionState.UnspecifiedWidth) {
+        if (offsetX == PaneExpansionState.Unspecified) {
             return
         }
         val placeables =
@@ -726,7 +721,7 @@
                 (paneLeft.placedPositionX + paneLeft.measuredWidth + paneRight.placedPositionX) / 2
             paneLeft.measuredAndPlaced -> paneLeft.placedPositionX + paneLeft.measuredWidth
             paneRight.measuredAndPlaced -> 0
-            else -> PaneExpansionState.UnspecifiedWidth
+            else -> PaneExpansionState.Unspecified
         }
     }
 }
@@ -830,15 +825,15 @@
 private class ThreePaneScaffoldScopeImpl(
     override val role: ThreePaneScaffoldRole,
     override val scaffoldStateTransition: Transition<ThreePaneScaffoldValue>,
-    private val transitionState: SeekableTransitionState<ThreePaneScaffoldValue>,
+    private val scaffoldState: ThreePaneScaffoldState,
     lookaheadScope: LookaheadScope
 ) : ThreePaneScaffoldScope, LookaheadScope by lookaheadScope, PaneScaffoldScopeImpl() {
     override val scaffoldStateTransitionFraction: Float
         get() =
-            if (transitionState.currentState == transitionState.targetState) {
+            if (scaffoldState.currentState == scaffoldState.targetState) {
                 1f
             } else {
-                transitionState.fraction
+                scaffoldState.progressFraction
             }
 
     override var positionAnimationSpec: FiniteAnimationSpec<IntOffset> by mutableStateOf(snap())
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
new file mode 100644
index 0000000..b7b8385
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.adaptive.layout
+
+import androidx.annotation.FloatRange
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.SeekableTransitionState
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.rememberTransition
+import androidx.compose.foundation.MutatorMutex
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Composable
+
+/**
+ * The state of a three pane scaffold. It serves as the [SeekableTransitionState] to manipulate the
+ * [Transition] between [ThreePaneScaffoldValue]s.
+ */
+@ExperimentalMaterial3AdaptiveApi
+class ThreePaneScaffoldState
+internal constructor(
+    private val transitionState: SeekableTransitionState<ThreePaneScaffoldValue>,
+) {
+    /** Constructs a [ThreePaneScaffoldState] with an initial value of [initialScaffoldValue]. */
+    constructor(
+        initialScaffoldValue: ThreePaneScaffoldValue
+    ) : this(SeekableTransitionState(initialScaffoldValue))
+
+    /**
+     * Current [ThreePaneScaffoldValue] state of the transition. If there is an active transition,
+     * [currentState] and [targetState] are different.
+     */
+    val currentState
+        get() = transitionState.currentState
+
+    /**
+     * Target [ThreePaneScaffoldValue] state of the transition. If this is the same as
+     * [currentState], no transition is active.
+     */
+    val targetState
+        get() = transitionState.targetState
+
+    /**
+     * The progress of the transition from [currentState] to [targetState] as a fraction of the
+     * entire duration.
+     *
+     * If [targetState] and [currentState] are the same, [progressFraction] will be 0.
+     */
+    @get:FloatRange(from = 0.0, to = 1.0)
+    val progressFraction
+        get() = transitionState.fraction
+
+    private val mutatorMutex = MutatorMutex()
+
+    /**
+     * Creates a [Transition] and puts it in the [currentState] of this [ThreePaneScaffoldState]. If
+     * [targetState] changes, the [Transition] will change where it will animate to.
+     *
+     * @param label The optional label for the transition.
+     */
+    @Composable
+    internal fun rememberTransition(label: String? = null): Transition<ThreePaneScaffoldValue> =
+        rememberTransition(transitionState, label)
+
+    /**
+     * Sets [currentState] and [targetState][ThreePaneScaffoldState.targetState] to [targetState]
+     * and snaps all values to those at that state. The transition will not have any animations
+     * running after running [snapTo].
+     *
+     * @param targetState The [ThreePaneScaffoldValue] state to snap to.
+     * @see SeekableTransitionState.snapTo
+     */
+    suspend fun snapTo(targetState: ThreePaneScaffoldValue) {
+        mutatorMutex.mutate { transitionState.snapTo(targetState) }
+    }
+
+    /**
+     * Seeks the transition to [targetState] with [fraction] used to indicate the progress towards
+     * [targetState].
+     *
+     * @param fraction The fractional progress of the transition.
+     * @param targetState The [ThreePaneScaffoldValue] state to seek to.
+     * @see SeekableTransitionState.seekTo
+     */
+    suspend fun seekTo(
+        @FloatRange(from = 0.0, to = 1.0) fraction: Float,
+        targetState: ThreePaneScaffoldValue = this.targetState,
+    ) {
+        mutatorMutex.mutate { transitionState.seekTo(fraction, targetState) }
+    }
+
+    /**
+     * Updates the current [targetState][ThreePaneScaffoldState.targetState] to [targetState] with
+     * an animation to the new state.
+     *
+     * @param targetState The [ThreePaneScaffoldValue] state to animate towards.
+     * @param animationSpec If provided, is used to animate the animation fraction. If `null`, the
+     *   transition is linearly traversed based on the duration of the transition.
+     * @see SeekableTransitionState.animateTo
+     */
+    suspend fun animateTo(
+        targetState: ThreePaneScaffoldValue = this.targetState,
+        animationSpec: FiniteAnimationSpec<Float>? = null,
+    ) {
+        mutatorMutex.mutate { transitionState.animateTo(targetState, animationSpec) }
+    }
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index 3388cc4..962ebd8 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -2,8 +2,8 @@
 package androidx.compose.material3.adaptive.navigation {
 
   public final class AndroidThreePaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
@@ -29,6 +29,7 @@
     method public boolean isDestinationHistoryAware();
     method public boolean navigateBack(optional String backNavigationBehavior);
     method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
     method public void setDestinationHistoryAware(boolean);
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
     property public abstract boolean isDestinationHistoryAware;
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index 3388cc4..962ebd8 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -2,8 +2,8 @@
 package androidx.compose.material3.adaptive.navigation {
 
   public final class AndroidThreePaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
@@ -29,6 +29,7 @@
     method public boolean isDestinationHistoryAware();
     method public boolean navigateBack(optional String backNavigationBehavior);
     method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
     method public void setDestinationHistoryAware(boolean);
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
     property public abstract boolean isDestinationHistoryAware;
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
index aa86c48..54e2cf5 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -521,6 +521,129 @@
     }
 
     @Test
+    fun singlePaneLayout_previousScaffoldValue_popLatest() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator =
+                rememberListDetailPaneScaffoldNavigator(
+                    scaffoldDirective = MockSinglePaneScaffoldDirective,
+                    initialDestinationHistory =
+                        listOf(
+                            ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                            ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                        )
+                )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            ListDetailPaneScaffoldRole.Detail]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            ListDetailPaneScaffoldRole.List]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            ListDetailPaneScaffoldRole.Detail]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            ListDetailPaneScaffoldRole.List]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_previousScaffoldValue_popUntilScaffoldValueChange() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator =
+                rememberListDetailPaneScaffoldNavigator(
+                    scaffoldDirective = MockSinglePaneScaffoldDirective,
+                    initialDestinationHistory =
+                        listOf(
+                            ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
+                            ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 0),
+                            ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, 1),
+                        )
+                )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[ListDetailPaneScaffoldRole.Detail]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[ListDetailPaneScaffoldRole.List]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilScaffoldValueChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[ListDetailPaneScaffoldRole.Detail]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[ListDetailPaneScaffoldRole.List]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
     fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
index c485993..d8c1561 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -573,6 +573,138 @@
     }
 
     @Test
+    fun singlePaneLayout_previousScaffoldValue_popLatest() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator =
+                rememberListDetailPaneScaffoldNavigator(
+                    scaffoldDirective = MockSinglePaneScaffoldDirective,
+                    initialDestinationHistory =
+                        listOf(
+                            ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
+                            ThreePaneScaffoldDestinationItem(
+                                SupportingPaneScaffoldRole.Supporting,
+                                0
+                            ),
+                        )
+                )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            SupportingPaneScaffoldRole.Supporting]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            SupportingPaneScaffoldRole.Main]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            SupportingPaneScaffoldRole.Supporting]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(BackNavigationBehavior.PopLatest)[
+                            SupportingPaneScaffoldRole.Main]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
+    fun singlePaneLayout_previousScaffoldValue_popUntilScaffoldValueChange() {
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
+
+        composeRule.setContent {
+            scaffoldNavigator =
+                rememberListDetailPaneScaffoldNavigator(
+                    scaffoldDirective = MockSinglePaneScaffoldDirective,
+                    initialDestinationHistory =
+                        listOf(
+                            ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main),
+                            ThreePaneScaffoldDestinationItem(
+                                SupportingPaneScaffoldRole.Supporting,
+                                0
+                            ),
+                            ThreePaneScaffoldDestinationItem(
+                                SupportingPaneScaffoldRole.Supporting,
+                                1
+                            ),
+                        )
+                )
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[SupportingPaneScaffoldRole.Supporting]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[SupportingPaneScaffoldRole.Main]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            scaffoldNavigator.navigateBack(BackNavigationBehavior.PopUntilScaffoldValueChange)
+        }
+
+        composeRule.runOnIdle {
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
+                .isEqualTo(PaneAdaptedValue.Expanded)
+
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[SupportingPaneScaffoldRole.Supporting]
+                )
+                .isEqualTo(PaneAdaptedValue.Hidden)
+            assertThat(
+                    scaffoldNavigator
+                        .peekPreviousScaffoldValue(
+                            BackNavigationBehavior.PopUntilScaffoldValueChange
+                        )[SupportingPaneScaffoldRole.Main]
+                )
+                .isEqualTo(PaneAdaptedValue.Expanded)
+        }
+    }
+
+    @Test
     fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
index 1efa9f4..e55f244 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
@@ -19,8 +19,11 @@
 import androidx.activity.compose.BackHandler
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold as BaseListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
+import androidx.compose.material3.adaptive.layout.PaneExpansionState
 import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold as BaseSupportingPaneScaffold
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
@@ -41,6 +44,13 @@
  *   mail app. See [ListDetailPaneScaffoldRole.Extra].
  * @param defaultBackBehavior the default back navigation behavior when the system back event
  *   happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -50,7 +60,9 @@
     detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange
+    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
     // TODO(b/330584029): support predictive back
     BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
@@ -62,7 +74,9 @@
         value = navigator.scaffoldValue,
         detailPane = detailPane,
         listPane = listPane,
-        extraPane = extraPane
+        extraPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
     )
 }
 
@@ -82,6 +96,13 @@
  *   [SupportingPaneScaffoldRole.Extra].
  * @param defaultBackBehavior the default back navigation behavior when the system back event
  *   happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -91,7 +112,9 @@
     supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange
+    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
     // TODO(b/330584029): support predictive back
     BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
@@ -103,6 +126,8 @@
         value = navigator.scaffoldValue,
         mainPane = mainPane,
         supportingPane = supportingPane,
-        extraPane = extraPane
+        extraPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
     )
 }
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
index 3feed63..81a8de2 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
@@ -79,6 +79,18 @@
     val scaffoldValue: ThreePaneScaffoldValue
 
     /**
+     * Returns the scaffold value associated with the previous destination, assuming there is a
+     * previous destination to navigate back to. If not, this is the same as [scaffoldValue].
+     *
+     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
+     *   during the back navigation. See [BackNavigationBehavior].
+     */
+    fun peekPreviousScaffoldValue(
+        backNavigationBehavior: BackNavigationBehavior =
+            BackNavigationBehavior.PopUntilScaffoldValueChange
+    ): ThreePaneScaffoldValue
+
+    /**
      * The current destination as tracked by the navigator.
      *
      * Implementors of this interface should ensure this value is updated whenever a navigation
@@ -330,6 +342,13 @@
         calculateScaffoldValue(destinationHistory.lastIndex)
     }
 
+    override fun peekPreviousScaffoldValue(
+        backNavigationBehavior: BackNavigationBehavior
+    ): ThreePaneScaffoldValue {
+        val index = getPreviousDestinationIndex(backNavigationBehavior)
+        return if (index == -1) scaffoldValue else calculateScaffoldValue(index)
+    }
+
     override fun navigateTo(pane: ThreePaneScaffoldRole, content: T?) {
         destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, content))
     }
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
index 3a44336..77e13de 100644
--- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -54,6 +54,9 @@
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
+import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
+import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
 import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
 import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
 import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
@@ -174,6 +177,14 @@
                     Text("Extra")
                 }
             }
+        },
+        paneExpansionState =
+            rememberPaneExpansionState(
+                keyProvider = scaffoldNavigator.scaffoldValue,
+                anchors = PaneExpansionAnchors
+            ),
+        paneExpansionDragHandle = { state ->
+            PaneExpansionDragHandle(state = state, color = MaterialTheme.colorScheme.outline)
         }
     )
 }
@@ -362,3 +373,11 @@
         }
     }
 }
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val PaneExpansionAnchors =
+    listOf(
+        PaneExpansionAnchor.Proportion(0f),
+        PaneExpansionAnchor.Proportion(0.5f),
+        PaneExpansionAnchor.Proportion(1f),
+    )
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 5f949cf..9638474 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -864,6 +864,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void SmallExtendedFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void SmallExtendedFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
     method @androidx.compose.runtime.Composable public static void SmallFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.ui.Modifier animateFloatingActionButton(androidx.compose.ui.Modifier, boolean visible, androidx.compose.ui.Alignment alignment, optional float targetScale, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? scaleAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? alphaAnimationSpec);
   }
 
   public final class FloatingActionButtonMenuKt {
@@ -1946,7 +1947,7 @@
 
   public final class ShortNavigationBarKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -2091,9 +2092,8 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class SplitButtonDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void AnimatedTrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.shape.CornerSize startCornerSize, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void LeadingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void TrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional boolean enabled, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void LeadingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SplitButtonShapes shapes, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void TrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SplitButtonShapes shapes, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method public androidx.compose.foundation.shape.CornerSize getInnerCornerSize();
     method public androidx.compose.foundation.layout.PaddingValues getLeadingButtonContentPadding();
     method public float getLeadingIconSize();
@@ -2101,8 +2101,8 @@
     method public float getSpacing();
     method public androidx.compose.foundation.layout.PaddingValues getTrailingButtonContentPadding();
     method public float getTrailingIconSize();
-    method public androidx.compose.foundation.shape.RoundedCornerShape leadingButtonShape(optional androidx.compose.foundation.shape.CornerSize endCornerSize);
-    method public androidx.compose.foundation.shape.RoundedCornerShape trailingButtonShape(optional androidx.compose.foundation.shape.CornerSize startCornerSize);
+    method public androidx.compose.material3.SplitButtonShapes leadingButtonShapes(optional androidx.compose.foundation.shape.CornerSize endCornerSize);
+    method public androidx.compose.material3.SplitButtonShapes trailingButtonShapes(optional androidx.compose.foundation.shape.CornerSize startCornerSize);
     property public final androidx.compose.foundation.shape.CornerSize InnerCornerSize;
     property public final androidx.compose.foundation.layout.PaddingValues LeadingButtonContentPadding;
     property public final float LeadingIconSize;
@@ -2121,6 +2121,20 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TonalSplitButton(kotlin.jvm.functions.Function0<kotlin.Unit> onLeadingButtonClick, kotlin.jvm.functions.Function0<kotlin.Unit> onTrailingButtonClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> leadingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> trailingContent, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.shape.CornerSize innerCornerSize, optional float spacing);
   }
 
+  public final class SplitButtonShapes {
+    ctor public SplitButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape? pressedShape, androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape component1();
+    method public androidx.compose.ui.graphics.Shape? component2();
+    method public androidx.compose.ui.graphics.Shape? component3();
+    method public androidx.compose.material3.SplitButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape? pressedShape, androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape? getCheckedShape();
+    method public androidx.compose.ui.graphics.Shape? getPressedShape();
+    method public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.ui.graphics.Shape? checkedShape;
+    property public final androidx.compose.ui.graphics.Shape? pressedShape;
+    property public final androidx.compose.ui.graphics.Shape shape;
+  }
+
   public final class SuggestionChipDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ChipColors elevatedSuggestionChipColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ChipColors elevatedSuggestionChipColors(optional long containerColor, optional long labelColor, optional long iconContentColor, optional long disabledContainerColor, optional long disabledLabelColor, optional long disabledIconContentColor);
@@ -2791,39 +2805,71 @@
   }
 
   @androidx.compose.runtime.Immutable public final class Typography {
-    ctor public Typography();
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Typography();
     ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall, optional androidx.compose.ui.text.TextStyle displayLargeEmphasized, optional androidx.compose.ui.text.TextStyle displayMediumEmphasized, optional androidx.compose.ui.text.TextStyle displaySmallEmphasized, optional androidx.compose.ui.text.TextStyle headlineLargeEmphasized, optional androidx.compose.ui.text.TextStyle headlineMediumEmphasized, optional androidx.compose.ui.text.TextStyle headlineSmallEmphasized, optional androidx.compose.ui.text.TextStyle titleLargeEmphasized, optional androidx.compose.ui.text.TextStyle titleMediumEmphasized, optional androidx.compose.ui.text.TextStyle titleSmallEmphasized, optional androidx.compose.ui.text.TextStyle bodyLargeEmphasized, optional androidx.compose.ui.text.TextStyle bodyMediumEmphasized, optional androidx.compose.ui.text.TextStyle bodySmallEmphasized, optional androidx.compose.ui.text.TextStyle labelLargeEmphasized, optional androidx.compose.ui.text.TextStyle labelMediumEmphasized, optional androidx.compose.ui.text.TextStyle labelSmallEmphasized);
     method public androidx.compose.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall, optional androidx.compose.ui.text.TextStyle displayLargeEmphasized, optional androidx.compose.ui.text.TextStyle displayMediumEmphasized, optional androidx.compose.ui.text.TextStyle displaySmallEmphasized, optional androidx.compose.ui.text.TextStyle headlineLargeEmphasized, optional androidx.compose.ui.text.TextStyle headlineMediumEmphasized, optional androidx.compose.ui.text.TextStyle headlineSmallEmphasized, optional androidx.compose.ui.text.TextStyle titleLargeEmphasized, optional androidx.compose.ui.text.TextStyle titleMediumEmphasized, optional androidx.compose.ui.text.TextStyle titleSmallEmphasized, optional androidx.compose.ui.text.TextStyle bodyLargeEmphasized, optional androidx.compose.ui.text.TextStyle bodyMediumEmphasized, optional androidx.compose.ui.text.TextStyle bodySmallEmphasized, optional androidx.compose.ui.text.TextStyle labelLargeEmphasized, optional androidx.compose.ui.text.TextStyle labelMediumEmphasized, optional androidx.compose.ui.text.TextStyle labelSmallEmphasized);
     method public androidx.compose.ui.text.TextStyle getBodyLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodyLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getBodyMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodyMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getBodySmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodySmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplayLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplayLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplayMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplayMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplaySmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplaySmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineSmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelSmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleSmallEmphasized();
     property public final androidx.compose.ui.text.TextStyle bodyLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodyLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle bodyMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodyMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle bodySmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodySmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle displayLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displayLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle displayMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displayMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle displaySmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displaySmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineSmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelSmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleSmallEmphasized;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WavyProgressIndicatorDefaults {
@@ -2919,7 +2965,7 @@
   public final class WideNavigationRailKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ModalExpandedNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.ModalExpandedNavigationRailState railState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional boolean gesturesEnabled, optional androidx.compose.material3.ModalExpandedNavigationRailProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRail(optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class WideNavigationRailStateKt {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 5f949cf..9638474 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -864,6 +864,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void SmallExtendedFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void SmallExtendedFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
     method @androidx.compose.runtime.Composable public static void SmallFloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional androidx.compose.material3.FloatingActionButtonElevation elevation, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.ui.Modifier animateFloatingActionButton(androidx.compose.ui.Modifier, boolean visible, androidx.compose.ui.Alignment alignment, optional float targetScale, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? scaleAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? alphaAnimationSpec);
   }
 
   public final class FloatingActionButtonMenuKt {
@@ -1946,7 +1947,7 @@
 
   public final class ShortNavigationBarKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -2091,9 +2092,8 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class SplitButtonDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void AnimatedTrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.shape.CornerSize startCornerSize, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void LeadingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void TrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional boolean enabled, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void LeadingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SplitButtonShapes shapes, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public void TrailingButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SplitButtonShapes shapes, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method public androidx.compose.foundation.shape.CornerSize getInnerCornerSize();
     method public androidx.compose.foundation.layout.PaddingValues getLeadingButtonContentPadding();
     method public float getLeadingIconSize();
@@ -2101,8 +2101,8 @@
     method public float getSpacing();
     method public androidx.compose.foundation.layout.PaddingValues getTrailingButtonContentPadding();
     method public float getTrailingIconSize();
-    method public androidx.compose.foundation.shape.RoundedCornerShape leadingButtonShape(optional androidx.compose.foundation.shape.CornerSize endCornerSize);
-    method public androidx.compose.foundation.shape.RoundedCornerShape trailingButtonShape(optional androidx.compose.foundation.shape.CornerSize startCornerSize);
+    method public androidx.compose.material3.SplitButtonShapes leadingButtonShapes(optional androidx.compose.foundation.shape.CornerSize endCornerSize);
+    method public androidx.compose.material3.SplitButtonShapes trailingButtonShapes(optional androidx.compose.foundation.shape.CornerSize startCornerSize);
     property public final androidx.compose.foundation.shape.CornerSize InnerCornerSize;
     property public final androidx.compose.foundation.layout.PaddingValues LeadingButtonContentPadding;
     property public final float LeadingIconSize;
@@ -2121,6 +2121,20 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TonalSplitButton(kotlin.jvm.functions.Function0<kotlin.Unit> onLeadingButtonClick, kotlin.jvm.functions.Function0<kotlin.Unit> onTrailingButtonClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> leadingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> trailingContent, boolean checked, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.shape.CornerSize innerCornerSize, optional float spacing);
   }
 
+  public final class SplitButtonShapes {
+    ctor public SplitButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape? pressedShape, androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape component1();
+    method public androidx.compose.ui.graphics.Shape? component2();
+    method public androidx.compose.ui.graphics.Shape? component3();
+    method public androidx.compose.material3.SplitButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape? pressedShape, androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape? getCheckedShape();
+    method public androidx.compose.ui.graphics.Shape? getPressedShape();
+    method public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.ui.graphics.Shape? checkedShape;
+    property public final androidx.compose.ui.graphics.Shape? pressedShape;
+    property public final androidx.compose.ui.graphics.Shape shape;
+  }
+
   public final class SuggestionChipDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ChipColors elevatedSuggestionChipColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ChipColors elevatedSuggestionChipColors(optional long containerColor, optional long labelColor, optional long iconContentColor, optional long disabledContainerColor, optional long disabledLabelColor, optional long disabledIconContentColor);
@@ -2791,39 +2805,71 @@
   }
 
   @androidx.compose.runtime.Immutable public final class Typography {
-    ctor public Typography();
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Typography();
     ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall, optional androidx.compose.ui.text.TextStyle displayLargeEmphasized, optional androidx.compose.ui.text.TextStyle displayMediumEmphasized, optional androidx.compose.ui.text.TextStyle displaySmallEmphasized, optional androidx.compose.ui.text.TextStyle headlineLargeEmphasized, optional androidx.compose.ui.text.TextStyle headlineMediumEmphasized, optional androidx.compose.ui.text.TextStyle headlineSmallEmphasized, optional androidx.compose.ui.text.TextStyle titleLargeEmphasized, optional androidx.compose.ui.text.TextStyle titleMediumEmphasized, optional androidx.compose.ui.text.TextStyle titleSmallEmphasized, optional androidx.compose.ui.text.TextStyle bodyLargeEmphasized, optional androidx.compose.ui.text.TextStyle bodyMediumEmphasized, optional androidx.compose.ui.text.TextStyle bodySmallEmphasized, optional androidx.compose.ui.text.TextStyle labelLargeEmphasized, optional androidx.compose.ui.text.TextStyle labelMediumEmphasized, optional androidx.compose.ui.text.TextStyle labelSmallEmphasized);
     method public androidx.compose.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall, optional androidx.compose.ui.text.TextStyle displayLargeEmphasized, optional androidx.compose.ui.text.TextStyle displayMediumEmphasized, optional androidx.compose.ui.text.TextStyle displaySmallEmphasized, optional androidx.compose.ui.text.TextStyle headlineLargeEmphasized, optional androidx.compose.ui.text.TextStyle headlineMediumEmphasized, optional androidx.compose.ui.text.TextStyle headlineSmallEmphasized, optional androidx.compose.ui.text.TextStyle titleLargeEmphasized, optional androidx.compose.ui.text.TextStyle titleMediumEmphasized, optional androidx.compose.ui.text.TextStyle titleSmallEmphasized, optional androidx.compose.ui.text.TextStyle bodyLargeEmphasized, optional androidx.compose.ui.text.TextStyle bodyMediumEmphasized, optional androidx.compose.ui.text.TextStyle bodySmallEmphasized, optional androidx.compose.ui.text.TextStyle labelLargeEmphasized, optional androidx.compose.ui.text.TextStyle labelMediumEmphasized, optional androidx.compose.ui.text.TextStyle labelSmallEmphasized);
     method public androidx.compose.ui.text.TextStyle getBodyLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodyLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getBodyMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodyMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getBodySmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getBodySmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplayLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplayLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplayMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplayMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getDisplaySmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getDisplaySmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getHeadlineSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getHeadlineSmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getLabelSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getLabelSmallEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleLargeEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleMedium();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleMediumEmphasized();
     method public androidx.compose.ui.text.TextStyle getTitleSmall();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.ui.text.TextStyle getTitleSmallEmphasized();
     property public final androidx.compose.ui.text.TextStyle bodyLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodyLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle bodyMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodyMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle bodySmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle bodySmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle displayLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displayLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle displayMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displayMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle displaySmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle displaySmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle headlineSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle headlineSmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle labelSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle labelSmallEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleLargeEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleMedium;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleMediumEmphasized;
     property public final androidx.compose.ui.text.TextStyle titleSmall;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.ui.text.TextStyle titleSmallEmphasized;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WavyProgressIndicatorDefaults {
@@ -2919,7 +2965,7 @@
   public final class WideNavigationRailKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ModalExpandedNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.ModalExpandedNavigationRailState railState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional boolean gesturesEnabled, optional androidx.compose.material3.ModalExpandedNavigationRailProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRail(optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class WideNavigationRailStateKt {
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 81f7036..4b3c7ab 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -31,6 +31,7 @@
 import androidx.compose.material3.samples.AlertDialogSample
 import androidx.compose.material3.samples.AlertDialogWithIconSample
 import androidx.compose.material3.samples.AnimatedExtendedFloatingActionButtonSample
+import androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
 import androidx.compose.material3.samples.AssistChipSample
 import androidx.compose.material3.samples.BasicAlertDialogSample
 import androidx.compose.material3.samples.BottomAppBarWithFAB
@@ -78,6 +79,8 @@
 import androidx.compose.material3.samples.ExitUntilCollapsedCenterAlignedMediumTopAppBarWithSubtitle
 import androidx.compose.material3.samples.ExitUntilCollapsedLargeTopAppBar
 import androidx.compose.material3.samples.ExitUntilCollapsedMediumTopAppBar
+import androidx.compose.material3.samples.ExpandableHorizontalFloatingAppBar
+import androidx.compose.material3.samples.ExpandableVerticalFloatingAppBar
 import androidx.compose.material3.samples.ExposedDropdownMenuSample
 import androidx.compose.material3.samples.ExtendedFloatingActionButtonSample
 import androidx.compose.material3.samples.ExtendedFloatingActionButtonTextSample
@@ -95,7 +98,6 @@
 import androidx.compose.material3.samples.FilterChipWithLeadingIconSample
 import androidx.compose.material3.samples.FloatingActionButtonMenuSample
 import androidx.compose.material3.samples.FloatingActionButtonSample
-import androidx.compose.material3.samples.HorizontalFloatingAppBar
 import androidx.compose.material3.samples.HorizontalMultiBrowseCarouselSample
 import androidx.compose.material3.samples.HorizontalUncontainedCarouselSample
 import androidx.compose.material3.samples.IconButtonSample
@@ -171,6 +173,8 @@
 import androidx.compose.material3.samples.ScaffoldWithIndefiniteSnackbar
 import androidx.compose.material3.samples.ScaffoldWithMultilineSnackbar
 import androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
+import androidx.compose.material3.samples.ScrollableHorizontalFloatingAppBar
+import androidx.compose.material3.samples.ScrollableVerticalFloatingAppBar
 import androidx.compose.material3.samples.ScrollingFancyIndicatorContainerTabs
 import androidx.compose.material3.samples.ScrollingPrimaryTextTabs
 import androidx.compose.material3.samples.ScrollingSecondaryTextTabs
@@ -230,7 +234,6 @@
 import androidx.compose.material3.samples.TonalToggleButtonSample
 import androidx.compose.material3.samples.TriStateCheckboxSample
 import androidx.compose.material3.samples.TwoLineListItem
-import androidx.compose.material3.samples.VerticalFloatingAppBar
 import androidx.compose.material3.samples.WideNavigationRailArrangementsSample
 import androidx.compose.material3.samples.WideNavigationRailCollapsedSample
 import androidx.compose.material3.samples.WideNavigationRailExpandedSample
@@ -800,18 +803,32 @@
 val FloatingAppBarsExamples =
     listOf(
         Example(
-            name = "HorizontalFloatingAppBar",
+            name = "ExpandableHorizontalFloatingAppBar",
             description = FloatingAppBarsExampleDescription,
             sourceUrl = FloatingAppBarsExampleSourceUrl,
         ) {
-            HorizontalFloatingAppBar()
+            ExpandableHorizontalFloatingAppBar()
         },
         Example(
-            name = "VerticalFloatingAppBar",
+            name = "ScrollableHorizontalFloatingAppBar",
             description = FloatingAppBarsExampleDescription,
             sourceUrl = FloatingAppBarsExampleSourceUrl,
         ) {
-            VerticalFloatingAppBar()
+            ScrollableHorizontalFloatingAppBar()
+        },
+        Example(
+            name = "ExpandableVerticalFloatingAppBar",
+            description = FloatingAppBarsExampleDescription,
+            sourceUrl = FloatingAppBarsExampleSourceUrl,
+        ) {
+            ExpandableVerticalFloatingAppBar()
+        },
+        Example(
+            name = "ScrollableVerticalFloatingAppBar",
+            description = FloatingAppBarsExampleDescription,
+            sourceUrl = FloatingAppBarsExampleSourceUrl,
+        ) {
+            ScrollableVerticalFloatingAppBar()
         }
     )
 
@@ -925,6 +942,13 @@
             LargeFloatingActionButtonSample()
         },
         Example(
+            name = "AnimatedFloatingActionButtonSample",
+            description = FloatingActionButtonsExampleDescription,
+            sourceUrl = FloatingActionButtonsExampleSourceUrl,
+        ) {
+            AnimatedFloatingActionButtonSample()
+        },
+        Example(
             name = "MediumFloatingActionButtonSample",
             description = FloatingActionButtonsExampleDescription,
             sourceUrl = FloatingActionButtonsExampleSourceUrl,
@@ -1415,7 +1439,7 @@
             PullToRefreshViewModelSample()
         },
         Example(
-            name = "PullToRefreshViewModelSample",
+            name = "PullToRefreshCustomIndicatorWithDefaultTransform",
             description = PullToRefreshExampleDescription,
             sourceUrl = PullToRefreshExampleSourceUrl
         ) {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonMenuSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonMenuSamples.kt
index 75e80f8..c2c140b 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonMenuSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonMenuSamples.kt
@@ -39,6 +39,7 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.ToggleFloatingActionButton
 import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon
+import androidx.compose.material3.animateFloatingActionButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
@@ -64,8 +65,11 @@
 @Sampled
 @Composable
 fun FloatingActionButtonMenuSample() {
+    val listState = rememberLazyListState()
+    val fabVisible by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
+
     Box {
-        LazyColumn(state = rememberLazyListState(), modifier = Modifier.fillMaxSize()) {
+        LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
             for (index in 0 until 100) {
                 item { Text(text = "List item - $index", modifier = Modifier.padding(24.dp)) }
             }
@@ -92,10 +96,14 @@
                 ToggleFloatingActionButton(
                     modifier =
                         Modifier.semantics {
-                            traversalIndex = -1f
-                            stateDescription = if (fabMenuExpanded) "Expanded" else "Collapsed"
-                            contentDescription = "Toggle menu"
-                        },
+                                traversalIndex = -1f
+                                stateDescription = if (fabMenuExpanded) "Expanded" else "Collapsed"
+                                contentDescription = "Toggle menu"
+                            }
+                            .animateFloatingActionButton(
+                                visible = fabVisible || fabMenuExpanded,
+                                alignment = Alignment.BottomEnd
+                            ),
                     checked = fabMenuExpanded,
                     onCheckedChange = { fabMenuExpanded = !fabMenuExpanded }
                 ) {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonSamples.kt
index 6fb74ca..1adc03e 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingActionButtonSamples.kt
@@ -38,10 +38,12 @@
 import androidx.compose.material3.SmallExtendedFloatingActionButton
 import androidx.compose.material3.SmallFloatingActionButton
 import androidx.compose.material3.Text
+import androidx.compose.material3.animateFloatingActionButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
@@ -99,6 +101,43 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun AnimatedFloatingActionButtonSample() {
+    val listState = rememberLazyListState()
+    // The FAB is initially shown. Upon scrolling past the first item we hide the FAB by using a
+    // remembered derived state to minimize unnecessary compositions.
+    val fabVisible by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
+
+    Scaffold(
+        floatingActionButton = {
+            MediumFloatingActionButton(
+                modifier =
+                    Modifier.animateFloatingActionButton(
+                        visible = fabVisible,
+                        alignment = Alignment.BottomEnd
+                    ),
+                onClick = { /* do something */ },
+            ) {
+                Icon(
+                    Icons.Filled.Add,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(FloatingActionButtonDefaults.MediumIconSize),
+                )
+            }
+        },
+        floatingActionButtonPosition = FabPosition.End,
+    ) {
+        LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
+            for (index in 0 until 100) {
+                item { Text(text = "List item - $index", modifier = Modifier.padding(24.dp)) }
+            }
+        }
+    }
+}
+
 @Preview
 @Sampled
 @Composable
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingAppBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingAppBarSamples.kt
index ad3cf48..cb7e259 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingAppBarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingAppBarSamples.kt
@@ -22,8 +22,10 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
@@ -33,6 +35,7 @@
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledIconButton
 import androidx.compose.material3.FloatingAppBarDefaults
 import androidx.compose.material3.FloatingAppBarDefaults.ScreenOffset
 import androidx.compose.material3.FloatingAppBarPosition.Companion.Bottom
@@ -58,7 +61,7 @@
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Sampled
 @Composable
-fun HorizontalFloatingAppBar() {
+fun ExpandableHorizontalFloatingAppBar() {
     val context = LocalContext.current
     val isTouchExplorationEnabled = remember {
         val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
@@ -70,9 +73,55 @@
         derivedStateOf {
             val temp = currentItem
             currentItem = listState.firstVisibleItemIndex
-            listState.firstVisibleItemIndex <= temp
+            listState.firstVisibleItemIndex <= temp // true if the list is scrolled up
         }
     }
+    Scaffold(
+        content = { innerPadding ->
+            Box {
+                LazyColumn(
+                    state = listState,
+                    contentPadding = innerPadding,
+                    verticalArrangement = Arrangement.spacedBy(8.dp)
+                ) {
+                    val list = (0..75).map { it.toString() }
+                    items(count = list.size) {
+                        Text(
+                            text = list[it],
+                            style = MaterialTheme.typography.bodyLarge,
+                            modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
+                        )
+                    }
+                }
+                HorizontalFloatingAppBar(
+                    modifier = Modifier.align(BottomCenter).offset(y = -ScreenOffset),
+                    expanded = expanded || isTouchExplorationEnabled,
+                    leadingContent = { leadingContent() },
+                    trailingContent = { trailingContent() },
+                    content = {
+                        FilledIconButton(
+                            modifier = Modifier.width(64.dp),
+                            onClick = { /* doSomething() */ }
+                        ) {
+                            Icon(Icons.Filled.Add, contentDescription = "Localized description")
+                        }
+                    },
+                )
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Sampled
+@Composable
+fun ScrollableHorizontalFloatingAppBar() {
+    val context = LocalContext.current
+    val isTouchExplorationEnabled = remember {
+        val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        am.isEnabled && am.isTouchExplorationEnabled
+    }
+    val listState = rememberLazyListState()
     val exitAlwaysScrollBehavior =
         FloatingAppBarDefaults.exitAlwaysScrollBehavior(position = Bottom)
     Scaffold(
@@ -95,9 +144,17 @@
                 }
                 HorizontalFloatingAppBar(
                     modifier = Modifier.align(BottomCenter).offset(y = -ScreenOffset),
-                    expanded = expanded || isTouchExplorationEnabled,
+                    expanded = true,
+                    leadingContent = { leadingContent() },
                     trailingContent = { trailingContent() },
-                    content = { mainContent() },
+                    content = {
+                        FilledIconButton(
+                            modifier = Modifier.width(64.dp),
+                            onClick = { /* doSomething() */ }
+                        ) {
+                            Icon(Icons.Filled.Add, contentDescription = "Localized description")
+                        }
+                    },
                     scrollBehavior =
                         if (!isTouchExplorationEnabled) exitAlwaysScrollBehavior else null,
                 )
@@ -109,7 +166,7 @@
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Sampled
 @Composable
-fun VerticalFloatingAppBar() {
+fun ExpandableVerticalFloatingAppBar() {
     val context = LocalContext.current
     val isTouchExplorationEnabled = remember {
         val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
@@ -121,9 +178,55 @@
         derivedStateOf {
             val temp = currentItem
             currentItem = listState.firstVisibleItemIndex
-            listState.firstVisibleItemIndex <= temp
+            listState.firstVisibleItemIndex <= temp // true if the list is scrolled up
         }
     }
+    Scaffold(
+        content = { innerPadding ->
+            Box {
+                LazyColumn(
+                    state = listState,
+                    contentPadding = innerPadding,
+                    verticalArrangement = Arrangement.spacedBy(8.dp)
+                ) {
+                    val list = (0..75).map { it.toString() }
+                    items(count = list.size) {
+                        Text(
+                            text = list[it],
+                            style = MaterialTheme.typography.bodyLarge,
+                            modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
+                        )
+                    }
+                }
+                VerticalFloatingAppBar(
+                    modifier = Modifier.align(CenterEnd).offset(x = -ScreenOffset),
+                    expanded = expanded || isTouchExplorationEnabled,
+                    leadingContent = { leadingContent() },
+                    trailingContent = { trailingContent() },
+                    content = {
+                        FilledIconButton(
+                            modifier = Modifier.height(64.dp),
+                            onClick = { /* doSomething() */ }
+                        ) {
+                            Icon(Icons.Filled.Add, contentDescription = "Localized description")
+                        }
+                    },
+                )
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Sampled
+@Composable
+fun ScrollableVerticalFloatingAppBar() {
+    val context = LocalContext.current
+    val isTouchExplorationEnabled = remember {
+        val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        am.isEnabled && am.isTouchExplorationEnabled
+    }
+    val listState = rememberLazyListState()
     val exitAlwaysScrollBehavior = FloatingAppBarDefaults.exitAlwaysScrollBehavior(position = End)
     Scaffold(
         modifier = Modifier.nestedScroll(exitAlwaysScrollBehavior),
@@ -145,9 +248,17 @@
                 }
                 VerticalFloatingAppBar(
                     modifier = Modifier.align(CenterEnd).offset(x = -ScreenOffset),
-                    expanded = expanded || isTouchExplorationEnabled,
+                    expanded = true,
+                    leadingContent = { leadingContent() },
                     trailingContent = { trailingContent() },
-                    content = { mainContent() },
+                    content = {
+                        FilledIconButton(
+                            modifier = Modifier.height(64.dp),
+                            onClick = { /* doSomething() */ }
+                        ) {
+                            Icon(Icons.Filled.Add, contentDescription = "Localized description")
+                        }
+                    },
                     scrollBehavior =
                         if (!isTouchExplorationEnabled) exitAlwaysScrollBehavior else null,
                 )
@@ -157,22 +268,19 @@
 }
 
 @Composable
-private fun mainContent() {
+private fun leadingContent() {
     IconButton(onClick = { /* doSomething() */ }) {
         Icon(Icons.Filled.Check, contentDescription = "Localized description")
     }
     IconButton(onClick = { /* doSomething() */ }) {
         Icon(Icons.Filled.Edit, contentDescription = "Localized description")
     }
-    IconButton(onClick = { /* doSomething() */ }) {
-        Icon(Icons.Filled.Download, contentDescription = "Localized description")
-    }
 }
 
 @Composable
 private fun trailingContent() {
     IconButton(onClick = { /* doSomething() */ }) {
-        Icon(Icons.Filled.Add, contentDescription = "Localized description")
+        Icon(Icons.Filled.Download, contentDescription = "Localized description")
     }
     IconButton(onClick = { /* doSomething() */ }) {
         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/PullToRefreshSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/PullToRefreshSamples.kt
index 728bc17..fc91ad3 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/PullToRefreshSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/PullToRefreshSamples.kt
@@ -36,6 +36,7 @@
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.LinearProgressIndicator
 import androidx.compose.material3.ListItem
+import androidx.compose.material3.ProgressIndicatorDefaults
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBar
@@ -55,6 +56,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.channels.Channel
@@ -443,13 +445,18 @@
             isRefreshing = isRefreshing,
             onRefresh = onRefresh,
             indicator = {
-                PullToRefreshDefaults.IndicatorBox(state = state, isRefreshing = isRefreshing) {
+                PullToRefreshDefaults.IndicatorBox(
+                    state = state,
+                    isRefreshing = isRefreshing,
+                    modifier = Modifier.align(Alignment.TopCenter),
+                    elevation = 0.dp
+                ) {
                     if (isRefreshing) {
-                        CircularProgressIndicator(modifier = Modifier.fillMaxWidth())
+                        CircularProgressIndicator()
                     } else {
                         CircularProgressIndicator(
-                            modifier = Modifier.fillMaxWidth(),
-                            progress = { state.distanceFraction }
+                            progress = { state.distanceFraction },
+                            trackColor = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
                         )
                     }
                 }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
index 13c3a29..a2e2d24 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
@@ -67,7 +67,7 @@
             }
         },
         trailingButton = {
-            SplitButtonDefaults.AnimatedTrailingButton(
+            SplitButtonDefaults.TrailingButton(
                 onClick = { checked = !checked },
                 checked = checked,
                 modifier =
@@ -262,7 +262,7 @@
             }
         },
         trailingButton = {
-            SplitButtonDefaults.AnimatedTrailingButton(
+            SplitButtonDefaults.TrailingButton(
                 onClick = { checked = !checked },
                 checked = checked,
             ) {
@@ -304,7 +304,7 @@
             }
         },
         trailingButton = {
-            SplitButtonDefaults.AnimatedTrailingButton(
+            SplitButtonDefaults.TrailingButton(
                 onClick = { checked = !checked },
                 checked = checked,
             ) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 94d3068..7a64ed6 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -16,13 +16,19 @@
 
 package androidx.compose.material3
 
+import android.os.Build
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.tokens.ExtendedFabPrimaryTokens
@@ -34,9 +40,13 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertShape
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInRoot
 import androidx.compose.ui.layout.onGloballyPositioned
@@ -52,6 +62,7 @@
 import androidx.compose.ui.test.assertTouchWidthIsEqualTo
 import androidx.compose.ui.test.assertWidthIsAtLeast
 import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
@@ -62,8 +73,10 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
+import androidx.core.graphics.ColorUtils
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
 import org.junit.Rule
@@ -711,8 +724,299 @@
             assertThat(shadowElevation.value).isEqualTo(5.dp)
         }
     }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun animateFloatingActionButton_hideBottomEnd_scalesAndFadesCorrectly() {
+        val visible = mutableStateOf(true)
+
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier =
+                    Modifier.background(Color.Red)
+                        .size(100.dp)
+                        .testTag(AnimateFloatingActionButtonTestTag)
+            ) {
+                Box(
+                    modifier =
+                        Modifier.animateFloatingActionButton(
+                                visible = visible.value,
+                                alignment = Alignment.BottomEnd,
+                                targetScale = 0.2f,
+                                scaleAnimationSpec = tween(100, easing = LinearEasing),
+                                alphaAnimationSpec = tween(100, easing = LinearEasing)
+                            )
+                            .background(Color.Blue, CircleShape)
+                            .fillMaxSize()
+                )
+            }
+        }
+
+        rule.runOnIdle { visible.value = false }
+
+        // Wait for initial recomposition / measure after state change
+        rule.mainClock.advanceTimeByFrame()
+
+        // Run half of the animation
+        rule.mainClock.advanceTimeBy(50)
+
+        rule
+            .onNodeWithTag(AnimateFloatingActionButtonTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor =
+                    Color(
+                        ColorUtils.compositeColors(
+                            Color.Blue.copy(alpha = 0.5f).toArgb(),
+                            Color.Red.toArgb()
+                        )
+                    ),
+                backgroundColor = Color.Red,
+                sizeX = with(rule.density) { 60.dp.toPx() },
+                sizeY = with(rule.density) { 60.dp.toPx() },
+                shapeSizeX = with(rule.density) { 60.dp.toPx() },
+                shapeSizeY = with(rule.density) { 60.dp.toPx() },
+                centerX = with(rule.density) { 70.dp.toPx() },
+                centerY = with(rule.density) { 70.dp.toPx() },
+                shapeOverlapPixelCount = with(rule.density) { 3.dp.toPx() }
+            )
+    }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun animateFloatingActionButton_hideCenter_scalesAndFadesCorrectly() {
+        val visible = mutableStateOf(true)
+
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier =
+                    Modifier.background(Color.Red)
+                        .size(100.dp)
+                        .testTag(AnimateFloatingActionButtonTestTag)
+            ) {
+                Box(
+                    modifier =
+                        Modifier.animateFloatingActionButton(
+                                visible = visible.value,
+                                alignment = Alignment.Center,
+                                targetScale = 0.2f,
+                                scaleAnimationSpec = tween(100, easing = LinearEasing),
+                                alphaAnimationSpec = tween(100, easing = LinearEasing)
+                            )
+                            .background(Color.Blue, CircleShape)
+                            .fillMaxSize()
+                )
+            }
+        }
+
+        rule.runOnIdle { visible.value = false }
+
+        // Wait for initial recomposition / measure after state change
+        rule.mainClock.advanceTimeByFrame()
+
+        // Run half of the animation
+        rule.mainClock.advanceTimeBy(50)
+
+        rule
+            .onNodeWithTag(AnimateFloatingActionButtonTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor =
+                    Color(
+                        ColorUtils.compositeColors(
+                            Color.Blue.copy(alpha = 0.5f).toArgb(),
+                            Color.Red.toArgb()
+                        )
+                    ),
+                backgroundColor = Color.Red,
+                sizeX = with(rule.density) { 60.dp.toPx() },
+                sizeY = with(rule.density) { 60.dp.toPx() },
+                shapeSizeX = with(rule.density) { 60.dp.toPx() },
+                shapeSizeY = with(rule.density) { 60.dp.toPx() },
+                centerX = with(rule.density) { 50.dp.toPx() },
+                centerY = with(rule.density) { 50.dp.toPx() },
+                shapeOverlapPixelCount = with(rule.density) { 2.dp.toPx() }
+            )
+    }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun animateFloatingActionButton_hideTopStart_scalesAndFadesCorrectly() {
+        val visible = mutableStateOf(true)
+
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier =
+                    Modifier.background(Color.Red)
+                        .size(100.dp)
+                        .testTag(AnimateFloatingActionButtonTestTag)
+            ) {
+                Box(
+                    modifier =
+                        Modifier.animateFloatingActionButton(
+                                visible = visible.value,
+                                alignment = Alignment.TopStart,
+                                targetScale = 0.2f,
+                                scaleAnimationSpec = tween(100, easing = LinearEasing),
+                                alphaAnimationSpec = tween(100, easing = LinearEasing)
+                            )
+                            .background(Color.Blue, CircleShape)
+                            .fillMaxSize()
+                )
+            }
+        }
+
+        rule.runOnIdle { visible.value = false }
+
+        // Wait for initial recomposition / measure after state change
+        rule.mainClock.advanceTimeByFrame()
+
+        // Run half of the animation
+        rule.mainClock.advanceTimeBy(50)
+
+        rule
+            .onNodeWithTag(AnimateFloatingActionButtonTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor =
+                    Color(
+                        ColorUtils.compositeColors(
+                            Color.Blue.copy(alpha = 0.5f).toArgb(),
+                            Color.Red.toArgb()
+                        )
+                    ),
+                backgroundColor = Color.Red,
+                sizeX = with(rule.density) { 60.dp.toPx() },
+                sizeY = with(rule.density) { 60.dp.toPx() },
+                shapeSizeX = with(rule.density) { 60.dp.toPx() },
+                shapeSizeY = with(rule.density) { 60.dp.toPx() },
+                centerX = with(rule.density) { 30.dp.toPx() },
+                centerY = with(rule.density) { 30.dp.toPx() },
+                shapeOverlapPixelCount = with(rule.density) { 2.dp.toPx() }
+            )
+    }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun animateFloatingActionButton_show_noScaleOrFadeAfterAnimation() {
+        val visible = mutableStateOf(false)
+
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier =
+                    Modifier.background(Color.Red)
+                        .size(100.dp)
+                        .testTag(AnimateFloatingActionButtonTestTag)
+            ) {
+                Box(
+                    modifier =
+                        Modifier.animateFloatingActionButton(
+                                visible = visible.value,
+                                alignment = Alignment.BottomEnd,
+                                targetScale = 0.2f,
+                                scaleAnimationSpec = tween(100, easing = LinearEasing),
+                                alphaAnimationSpec = tween(100, easing = LinearEasing)
+                            )
+                            .background(Color.Blue, CircleShape)
+                            .fillMaxSize()
+                )
+            }
+        }
+
+        rule.runOnIdle { visible.value = true }
+
+        // Wait for initial recomposition / measure after state change
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        // Run full animation
+        rule.mainClock.advanceTimeBy(100)
+
+        rule
+            .onNodeWithTag(AnimateFloatingActionButtonTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor = Color.Blue,
+                backgroundColor = Color.Red,
+                sizeX = with(rule.density) { 100.dp.toPx() },
+                sizeY = with(rule.density) { 100.dp.toPx() },
+                shapeSizeX = with(rule.density) { 100.dp.toPx() },
+                shapeSizeY = with(rule.density) { 100.dp.toPx() },
+                centerX = with(rule.density) { 50.dp.toPx() },
+                centerY = with(rule.density) { 50.dp.toPx() },
+                shapeOverlapPixelCount = with(rule.density) { 2.dp.toPx() }
+            )
+    }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun animateFloatingActionButton_show_noScaleOrFadeBeforeAnimation() {
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier =
+                    Modifier.background(Color.Red)
+                        .size(100.dp)
+                        .testTag(AnimateFloatingActionButtonTestTag)
+            ) {
+                Box(
+                    modifier =
+                        Modifier.animateFloatingActionButton(
+                                visible = true,
+                                alignment = Alignment.BottomEnd,
+                                targetScale = 0.2f,
+                                scaleAnimationSpec = tween(100, easing = LinearEasing),
+                                alphaAnimationSpec = tween(100, easing = LinearEasing)
+                            )
+                            .background(Color.Blue, CircleShape)
+                            .fillMaxSize()
+                )
+            }
+        }
+
+        rule
+            .onNodeWithTag(AnimateFloatingActionButtonTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor = Color.Blue,
+                backgroundColor = Color.Red,
+                sizeX = with(rule.density) { 100.dp.toPx() },
+                sizeY = with(rule.density) { 100.dp.toPx() },
+                shapeSizeX = with(rule.density) { 100.dp.toPx() },
+                shapeSizeY = with(rule.density) { 100.dp.toPx() },
+                centerX = with(rule.density) { 50.dp.toPx() },
+                centerY = with(rule.density) { 50.dp.toPx() },
+                shapeOverlapPixelCount = with(rule.density) { 2.dp.toPx() }
+            )
+    }
 }
 
+private val AnimateFloatingActionButtonTestTag = "AnimateFloatingActionButton"
+
 fun assertWithinOnePixel(expected: Offset, actual: Offset) {
     assertWithinOnePixel(expected.x, actual.x)
     assertWithinOnePixel(expected.y, actual.y)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index eadf4b8..0aa561b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -19,7 +19,10 @@
 import android.content.ComponentCallbacks2
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
+import android.os.Build
 import android.os.Build.VERSION.SDK_INT
+import android.os.Bundle
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.compose.foundation.ScrollState
@@ -35,6 +38,8 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.internal.Strings
+import androidx.compose.material3.internal.getString
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.derivedStateOf
@@ -53,6 +58,7 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.SemanticsMatcher
@@ -65,6 +71,7 @@
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onParent
 import androidx.compose.ui.test.performClick
@@ -82,6 +89,7 @@
 import androidx.compose.ui.unit.width
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.google.common.truth.Truth.assertThat
@@ -1117,4 +1125,31 @@
         // Size entirely filled by padding provided by WindowInsetPadding
         rule.onNodeWithTag(sheetTag).onParent().assertHeightIsEqualTo(sheetHeight)
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun modalBottomSheet_assertSheetContentIsReadBeforeScrim() {
+        lateinit var composeView: View
+        var closeSheet = ""
+        rule.setContent {
+            closeSheet = getString(Strings.CloseSheet)
+            ModalBottomSheet(onDismissRequest = {}, modifier = Modifier.testTag(sheetTag)) {
+                composeView = LocalView.current
+                Box(Modifier.fillMaxWidth().height(sheetHeight))
+            }
+        }
+
+        val scrimViewId = rule.onNodeWithContentDescription(closeSheet).fetchSemanticsNode().id
+        val sheetViewId = rule.onNodeWithTag(sheetTag).fetchSemanticsNode().id
+
+        rule.runOnUiThread {
+            val accessibilityNodeProvider = composeView.accessibilityNodeProvider
+            val sheetViewANI = accessibilityNodeProvider.createAccessibilityNodeInfo(sheetViewId)
+            // Ensure that sheet A11y info is read before scrim view.
+            assertThat(sheetViewANI?.extras?.traversalBefore).isAtMost(scrimViewId)
+        }
+    }
+
+    private val Bundle.traversalBefore: Int
+        get() = getInt("android.view.accessibility.extra.EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL")
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailScreenshotTest.kt
index 2dfc1a5..a3a1596 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailScreenshotTest.kt
@@ -41,7 +41,6 @@
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class ModalExpandedNavigationRailScreenshotTest {
-    // TODO: Add screenshot tests for predictive back behavior.
 
     @get:Rule val composeTestRule = createComposeRule()
 
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
index 33d5274..97d4380 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.getString
+import androidx.compose.material3.tokens.NavigationRailExpandedTokens
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
@@ -80,7 +81,7 @@
 
     @Test
     fun modalRail_closes() {
-        val railWidth = 220.dp // TODO: Replace with token.
+        val railWidth = NavigationRailExpandedTokens.ContainerWidthMinimum
         lateinit var railState: ModalExpandedNavigationRailState
         lateinit var scope: CoroutineScope
 
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
index de88ed2..d16821e 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
@@ -166,7 +166,7 @@
 
     @Test
     fun navigationBar_size() {
-        val height = NavigationBarTokens.ContainerHeight
+        val height = NavigationBarTokens.TallContainerHeight
         rule
             .setMaterialContentForSizeAssertions {
                 val items = listOf("Songs", "Artists", "Playlists")
@@ -230,7 +230,7 @@
                 totalWidth.toFloat() - (NavigationBarItemHorizontalPadding.toPx() * 3)
 
             val expectedItemWidth = (availableWidth / 4)
-            val expectedItemHeight = NavigationBarTokens.ContainerHeight.toPx()
+            val expectedItemHeight = NavigationBarTokens.TallContainerHeight.toPx()
 
             assertThat(itemCoords.size).isEqualTo(4)
 
@@ -252,11 +252,11 @@
                 NavigationBarItem(
                     icon = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.ActiveIconColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemActiveIconColor.value)
                     },
                     label = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.ActiveLabelTextColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemActiveLabelTextColor.value)
                     },
                     selected = true,
                     onClick = {}
@@ -264,11 +264,11 @@
                 NavigationBarItem(
                     icon = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.InactiveIconColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemInactiveIconColor.value)
                     },
                     label = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.InactiveLabelTextColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemInactiveLabelTextColor.value)
                     },
                     selected = false,
                     onClick = {}
@@ -292,7 +292,7 @@
                     icon = { assertThat(LocalContentColor.current).isEqualTo(Color.Red) },
                     label = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.ActiveLabelTextColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemActiveLabelTextColor.value)
                     },
                     selected = true,
                     onClick = {}
@@ -301,7 +301,7 @@
                     colors = customNavigationBarItemColors,
                     icon = {
                         assertThat(LocalContentColor.current)
-                            .isEqualTo(NavigationBarTokens.InactiveIconColor.value)
+                            .isEqualTo(NavigationBarTokens.ItemInactiveIconColor.value)
                     },
                     label = { assertThat(LocalContentColor.current).isEqualTo(Color.Green) },
                     selected = false,
@@ -313,7 +313,7 @@
 
     @Test
     fun navigationBarItem_withLongLabel_automaticallyResizesHeight() {
-        val defaultHeight = NavigationBarTokens.ContainerHeight
+        val defaultHeight = NavigationBarTokens.TallContainerHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             NavigationBar(modifier = Modifier.testTag("TAG")) {
@@ -432,7 +432,7 @@
 
     @Test
     fun navigationBarItemContent_customHeight_withLabel_sizeAndPosition() {
-        val defaultHeight = NavigationBarTokens.ContainerHeight
+        val defaultHeight = NavigationBarTokens.TallContainerHeight
         val customHeight = 64.dp
 
         rule.setMaterialContent(lightColorScheme()) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
index 14faa8d..b119e6e 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.tokens.NavigationBarTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.testutils.assertIsEqualTo
@@ -94,7 +95,7 @@
 
     @Test
     fun bar_size() {
-        val height = 64.dp // TODO: Replace with token.
+        val height = NavigationBarTokens.ContainerHeight
         rule
             .setMaterialContentForSizeAssertions {
                 ShortNavigationBar {
@@ -154,7 +155,7 @@
         rule.runOnIdleWithDensity {
             val width = parentCoords.size.width
             val expectedItemWidth = (width / 4f)
-            val expectedItemHeight = 64.dp.toPx() // TODO: Replace with token.
+            val expectedItemHeight = NavigationBarTokens.ContainerHeight.toPx()
 
             Truth.assertThat(itemCoords.size).isEqualTo(4)
             itemCoords.forEach { (index, coord) ->
@@ -169,7 +170,7 @@
 
     @Test
     fun bar_equalWeightArrangement_topIconItemWithLongLabel_automaticallyResizesHeight() {
-        val defaultHeight = 64.dp // TODO: Replace with token.
+        val defaultHeight = NavigationBarTokens.ContainerHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             ShortNavigationBar(modifier = Modifier.testTag("TAG")) {
@@ -350,7 +351,7 @@
 
     @Test
     fun bar_centeredArrangement_startIconItemWithLongLabel_automaticallyResizesHeight() {
-        val defaultHeight = 64.dp // TODO: Replace with token.
+        val defaultHeight = NavigationBarTokens.ContainerHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             ShortNavigationBar(
@@ -480,6 +481,7 @@
             ShortNavigationBarItem(
                 modifier = Modifier.testTag("item"),
                 icon = { Icon(Icons.Filled.Favorite, "Favorite") },
+                label = null,
                 selected = false,
                 onClick = {}
             )
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonScreenshotTest.kt
index 343bbe2..06b13ec 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonScreenshotTest.kt
@@ -72,7 +72,7 @@
                         }
                     },
                     trailingButton = {
-                        SplitButtonDefaults.AnimatedTrailingButton(
+                        SplitButtonDefaults.TrailingButton(
                             onClick = {},
                             checked = false,
                         ) {
@@ -144,18 +144,14 @@
                         Text("My Button")
                     },
                     trailingContent = {
-                        Box(
-                            modifier = Modifier.fillMaxHeight(),
-                            contentAlignment = Alignment.Center
-                        ) {
-                            Icon(
-                                Icons.Outlined.KeyboardArrowDown,
-                                modifier =
-                                    Modifier.size(SplitButtonDefaults.TrailingIconSize)
-                                        .graphicsLayer { this.rotationZ = 180f },
-                                contentDescription = "Localized description"
-                            )
-                        }
+                        Icon(
+                            Icons.Outlined.KeyboardArrowDown,
+                            modifier =
+                                Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
+                                    this.rotationZ = 180f
+                                },
+                            contentDescription = "Localized description"
+                        )
                     }
                 )
             }
@@ -280,7 +276,7 @@
                         }
                     },
                     trailingButton = {
-                        SplitButtonDefaults.AnimatedTrailingButton(
+                        SplitButtonDefaults.TrailingButton(
                             onClick = {},
                             checked = false,
                         ) {
@@ -311,7 +307,7 @@
                         }
                     },
                     trailingButton = {
-                        SplitButtonDefaults.AnimatedTrailingButton(onClick = {}, checked = false) {
+                        SplitButtonDefaults.TrailingButton(onClick = {}, checked = false) {
                             Icon(
                                 Icons.Outlined.KeyboardArrowDown,
                                 contentDescription = "Localized description",
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonTest.kt
index 021104b..bb8c0ba 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SplitButtonTest.kt
@@ -69,8 +69,8 @@
                 trailingButton = {
                     SplitButtonDefaults.TrailingButton(
                         modifier = Modifier.size(34.dp).testTag("trailingButton"),
+                        checked = false,
                         onClick = {},
-                        shape = SplitButtonDefaults.trailingButtonShape(),
                     ) {
                         Icon(Icons.Outlined.KeyboardArrowDown, contentDescription = "Trailing Icon")
                     }
@@ -99,9 +99,9 @@
                 },
                 trailingButton = {
                     SplitButtonDefaults.TrailingButton(
-                        modifier = Modifier.size(34.dp).testTag("trailing button"),
                         onClick = {},
-                        shape = SplitButtonDefaults.trailingButtonShape(),
+                        checked = false,
+                        modifier = Modifier.size(34.dp).testTag("trailing button"),
                     ) {
                         Icon(Icons.Outlined.KeyboardArrowDown, contentDescription = "Trailing Icon")
                     }
@@ -134,9 +134,9 @@
                 },
                 trailingButton = {
                     SplitButtonDefaults.TrailingButton(
-                        modifier = Modifier.size(34.dp).testTag("trailing button"),
                         onClick = {},
-                        shape = SplitButtonDefaults.trailingButtonShape(),
+                        checked = false,
+                        modifier = Modifier.size(34.dp).testTag("trailing button"),
                         enabled = false,
                     ) {
                         Icon(Icons.Outlined.KeyboardArrowDown, contentDescription = "Trailing Icon")
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonTest.kt
index 41caaaa..a4b9fc7 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.tokens.ElevatedButtonTokens
 import androidx.compose.material3.tokens.FilledButtonTokens
 import androidx.compose.material3.tokens.OutlinedButtonTokens
@@ -31,6 +32,7 @@
 import androidx.compose.testutils.assertIsEqualTo
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -300,4 +302,30 @@
                 )
         }
     }
+
+    @Test
+    fun buttonShapes_AllRounded_hasRoundedShapesIsTrue() {
+        assertThat(
+                ButtonShapes(
+                        shape = RoundedCornerShape(10.dp),
+                        pressedShape = RoundedCornerShape(10.dp),
+                        checkedShape = RoundedCornerShape(4.dp),
+                    )
+                    .hasRoundedCornerShapes
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun buttonShapes_mixedShapes_hasRoundedShapesIsFalse() {
+        assertThat(
+                ButtonShapes(
+                        shape = RectangleShape,
+                        pressedShape = RoundedCornerShape(10.dp),
+                        checkedShape = RoundedCornerShape(4.dp),
+                    )
+                    .hasRoundedCornerShapes
+            )
+            .isFalse()
+    }
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
index 0d45f3e..fdfaeea 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
@@ -24,6 +24,9 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.tokens.NavigationRailBaselineItemTokens
+import androidx.compose.material3.tokens.NavigationRailCollapsedTokens
+import androidx.compose.material3.tokens.NavigationRailExpandedTokens
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -69,9 +72,10 @@
 class WideNavigationRailTest {
     @get:Rule val rule = createComposeRule()
 
-    private val collapsedWidth = 96.dp // TODO: Replace with token.
-    private val expandedMinWidth = 220.dp // TODO: Replace with token.
-    private val expandedMaxWidth = 360.dp // TODO: Replace with token.
+    private val collapsedWidth = NavigationRailCollapsedTokens.ContainerWidth
+    private val expandedMinWidth = NavigationRailExpandedTokens.ContainerWidthMinimum
+    private val expandedMaxWidth = NavigationRailExpandedTokens.ContainerWidthMaximum
+    private val verticalPadding = NavigationRailCollapsedTokens.TopSpace
 
     @Test
     fun rail_defaultSemantics() {
@@ -209,7 +213,7 @@
         }
         rule
             .onNodeWithTag("content")
-            .assertTopPositionInRootIsEqualTo(13.dp + WNRVerticalPadding)
+            .assertTopPositionInRootIsEqualTo(13.dp + verticalPadding)
             .assertLeftPositionInRootIsEqualTo(13.dp)
     }
 
@@ -336,6 +340,7 @@
             WideNavigationRailItem(
                 modifier = Modifier.testTag("item"),
                 icon = { Icon(Icons.Filled.Favorite, "Favorite") },
+                label = null,
                 selected = false,
                 onClick = {}
             )
@@ -368,7 +373,7 @@
 
     @Test
     fun item_topIconPosition_withLongLabel_automaticallyResizesHeight() {
-        val defaultHeight = WNRTopIconItemMinHeight
+        val defaultHeight = NavigationRailBaselineItemTokens.ContainerHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             WideNavigationRailItem(
@@ -414,6 +419,7 @@
             WideNavigationRailItem(
                 modifier = Modifier.testTag("item"),
                 icon = { Icon(Icons.Filled.Favorite, null, Modifier.testTag("icon")) },
+                label = null,
                 selected = true,
                 onClick = {}
             )
@@ -456,14 +462,16 @@
         // Header should always be at the top.
         rule
             .onNodeWithTag("header", useUnmergedTree = true)
-            .assertTopPositionInRootIsEqualTo(WNRVerticalPadding)
+            .assertTopPositionInRootIsEqualTo(verticalPadding)
             .assertLeftPositionInRootIsEqualTo(0.dp)
 
         // Item should be `HeaderPadding` below the header in top arrangement.
         rule
             .onNodeWithTag("item", useUnmergedTree = true)
             .assertTopPositionInRootIsEqualTo(
-                WNRVerticalPadding + headerBounds.height + WNRHeaderPadding
+                verticalPadding +
+                    headerBounds.height +
+                    NavigationRailBaselineItemTokens.HeaderSpaceMinimum
             )
     }
 
@@ -491,7 +499,7 @@
         // Header should always be at the top.
         rule
             .onNodeWithTag("header", useUnmergedTree = true)
-            .assertTopPositionInRootIsEqualTo(WNRVerticalPadding)
+            .assertTopPositionInRootIsEqualTo(verticalPadding)
             .assertLeftPositionInRootIsEqualTo(0.dp)
         // Assert item is centered.
         rule
@@ -523,13 +531,13 @@
         // Header should always be at the top.
         rule
             .onNodeWithTag("header", useUnmergedTree = true)
-            .assertTopPositionInRootIsEqualTo(WNRVerticalPadding)
+            .assertTopPositionInRootIsEqualTo(verticalPadding)
             .assertLeftPositionInRootIsEqualTo(0.dp)
         // Assert item is at the bottom.
         rule
             .onNodeWithTag("item", useUnmergedTree = true)
             .assertTopPositionInRootIsEqualTo(
-                (railBounds.height - WNRVerticalPadding - itemBounds.height)
+                (railBounds.height - verticalPadding - itemBounds.height)
             )
     }
 }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/WideNavigationRail.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/WideNavigationRail.android.kt
index 08b8371..94554e1 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/WideNavigationRail.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/WideNavigationRail.android.kt
@@ -127,6 +127,7 @@
     properties: ModalExpandedNavigationRailProperties,
     onPredictiveBack: (Float) -> Unit,
     onPredictiveBackCancelled: () -> Unit,
+    predictiveBackState: RailPredictiveBackState,
     content: @Composable () -> Unit
 ) {
     val view = LocalView.current
@@ -147,6 +148,7 @@
                     dialogId,
                     onPredictiveBack,
                     onPredictiveBackCancelled,
+                    predictiveBackState,
                     darkThemeEnabled,
                 )
                 .apply {
@@ -188,6 +190,8 @@
     private val onDismissRequest: () -> Unit,
     private val onPredictiveBack: (Float) -> Unit,
     private val onPredictiveBackCancelled: () -> Unit,
+    private val predictiveBackState: RailPredictiveBackState,
+    private val layoutDirection: LayoutDirection,
 ) : AbstractComposeView(context), DialogWindowProvider {
 
     private var content: @Composable () -> Unit by mutableStateOf({})
@@ -232,9 +236,11 @@
             backCallback =
                 if (Build.VERSION.SDK_INT >= 34) {
                     Api34Impl.createBackCallback(
-                        onDismissRequest,
-                        onPredictiveBack,
-                        onPredictiveBackCancelled,
+                        onDismissRequest = onDismissRequest,
+                        onPredictiveBack = onPredictiveBack,
+                        onPredictiveBackCancelled = onPredictiveBackCancelled,
+                        predictiveBackState = predictiveBackState,
+                        layoutDirection = layoutDirection
                     )
                 } else {
                     Api33Impl.createBackCallback(onDismissRequest)
@@ -258,13 +264,23 @@
             onDismissRequest: () -> Unit,
             onPredictiveBack: (Float) -> Unit,
             onPredictiveBackCancelled: () -> Unit,
+            predictiveBackState: RailPredictiveBackState,
+            layoutDirection: LayoutDirection
         ) =
             object : OnBackAnimationCallback {
                 override fun onBackStarted(backEvent: BackEvent) {
+                    predictiveBackState.update(
+                        isSwipeEdgeLeft = backEvent.swipeEdge == BackEvent.EDGE_LEFT,
+                        isRtl = layoutDirection == LayoutDirection.Rtl
+                    )
                     onPredictiveBack(PredictiveBack.transform(backEvent.progress))
                 }
 
                 override fun onBackProgressed(backEvent: BackEvent) {
+                    predictiveBackState.update(
+                        isSwipeEdgeLeft = backEvent.swipeEdge == BackEvent.EDGE_LEFT,
+                        isRtl = layoutDirection == LayoutDirection.Rtl
+                    )
                     onPredictiveBack(PredictiveBack.transform(backEvent.progress))
                 }
 
@@ -321,6 +337,7 @@
     dialogId: UUID,
     onPredictiveBack: (Float) -> Unit,
     onPredictiveBackCancelled: () -> Unit,
+    predictiveBackState: RailPredictiveBackState,
     darkThemeEnabled: Boolean,
 ) :
     ComponentDialog(
@@ -347,12 +364,14 @@
         WindowCompat.setDecorFitsSystemWindows(window, false)
         dialogLayout =
             ModalWideNavigationRailDialogLayout(
-                    context,
-                    window,
-                    properties.shouldDismissOnBackPress,
-                    onDismissRequest,
-                    onPredictiveBack,
-                    onPredictiveBackCancelled,
+                    context = context,
+                    window = window,
+                    shouldDismissOnBackPress = properties.shouldDismissOnBackPress,
+                    onDismissRequest = onDismissRequest,
+                    onPredictiveBack = onPredictiveBack,
+                    onPredictiveBackCancelled = onPredictiveBackCancelled,
+                    predictiveBackState = predictiveBackState,
+                    layoutDirection = layoutDirection,
                 )
                 .apply {
                     // Set unique id for AbstractComposeView. This allows state restoration for the
diff --git a/compose/material3/material3/src/androidMain/res/values-af/strings.xml b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
index 24dcf60..26474cd 100644
--- a/compose/material3/material3/src/androidMain/res/values-af/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Uur"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"vir minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"vir uur"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Maak spoor toe"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigasiespoor"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-am/strings.xml b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
index c8a41c4..707e5cc 100644
--- a/compose/material3/material3/src/androidMain/res/values-am/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ሰዓት"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ለደቂቃዎች"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ለሰዓት"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ሐዲድ ዝጋ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"የአሰሳ ሐዲድ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
index b1b6b53..1470702 100644
--- a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ساعة"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"الدقائق"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"الساعات"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"إغلاق الشريط"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"شريط التنقّل"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-as/strings.xml b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
index 7a754e4..db261f9 100644
--- a/compose/material3/material3/src/androidMain/res/values-as/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ঘণ্টা"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"মিনিটৰ বাবে"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ঘণ্টাৰ বাবে"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ৰে’ল কৰক"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"নেভিগেশ্বন ৰে’ল"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-az/strings.xml b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
index 880a2f6..63599d2 100644
--- a/compose/material3/material3/src/androidMain/res/values-az/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Saat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"dəqiqəlik"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"saatlıq"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Relsi bağlayın"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Naviqasiya relsi"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
index 74e0c93..c3b16e5 100644
--- a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Sat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"za minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"za sate"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zatvorite traku"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Traka za navigaciju"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-be/strings.xml b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
index 9331c27..97ab56e 100644
--- a/compose/material3/material3/src/androidMain/res/values-be/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Гадзіны"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"хвіліны"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"гадзіны"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Закрыць планку"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Планка навігацыі"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
index a8f0562..dedf5bf 100644
--- a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Час"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"за минутите"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"за часа"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Затваряне на лентата"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Лента за навигация"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
index f5c2cae..413e0af 100644
--- a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ঘণ্টা"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"এত মিনিটের জন্য"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"এত ঘণ্টার জন্য"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"রেল বন্ধ করুন"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"নেভিগেশন রেল"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
index d28d7c6..35bf7ca 100644
--- a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Sat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"za minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"za sat"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zatvaranje trake"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Traka za navigaciju"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
index 4cf1d63..76bc510 100644
--- a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"per als minuts"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"per a l\'hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Tanca la barra"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Barra de navegació"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
index 464b922..71a3014 100644
--- a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hodina"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"pro minuty"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"pro hodinu"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zavřít sloupec"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigační sloupec"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-da/strings.xml b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
index 8f67760..c124b85 100644
--- a/compose/material3/material3/src/androidMain/res/values-da/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Time"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"til minutter"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"til timer"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Luk bjælke"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigationsbjælke"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-de/strings.xml b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
index 7e3dd17..c0d62e6 100644
--- a/compose/material3/material3/src/androidMain/res/values-de/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Stunde"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"für Minuten"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"für Stunde"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Streifen schließen"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigationsstreifen"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-el/strings.xml b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
index 9f50fbf..0f944da 100644
--- a/compose/material3/material3/src/androidMain/res/values-el/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ώρα"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"για λεπτά"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"για ώρα"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Κλείσιμο στήλης"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Στήλη πλοήγησης"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
index 5f149ba..8d31a9b 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hour"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"for minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"for hour"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Close rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
index 00feb54..82d3750 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hour"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"for minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"for hour"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Close rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
index 5f149ba..8d31a9b 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hour"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"for minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"for hour"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Close rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
index 5f149ba..8d31a9b 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hour"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"for minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"for hour"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Close rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
index 032704e..52ab374 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎Hour‎‏‎‎‏‎"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎for minutes‎‏‎‎‏‎"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎for hour‎‏‎‎‏‎"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎Close rail‎‏‎‎‏‎"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎Navigation rail‎‏‎‎‏‎"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
index e10b3e7..7a42459 100644
--- a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"por minutos"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"por hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Cerrar riel"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Riel de navegación"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-es/strings.xml b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
index 362f3ee..305228d 100644
--- a/compose/material3/material3/src/androidMain/res/values-es/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"para los minutos"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"para la hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Cerrar barra"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Barra de navegación"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-et/strings.xml b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
index bf986c4..04ee801 100644
--- a/compose/material3/material3/src/androidMain/res/values-et/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Tunnid"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minutite jaoks"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"tundide jaoks"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Modaalraja sulgemine"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigeerimisrada"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
index f9ca8bf..98816ba 100644
--- a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ordua"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minutuetarako"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ordurako"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Itxi erraila"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Nabigazio-erraila"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
index e1a071e..0c5a1a0 100644
--- a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ساعت"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"برای دقیقه"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"برای ساعت"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"بستن ریل"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ریل پیمایش"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
index cc593b8..03d7945 100644
--- a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Tunti"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minuuttien osalta"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"tuntien osalta"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Sulje palkki"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Siirtymispalkki"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
index 67842aa..f0fb386 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Heure"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"pour les minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"pour l\'heure"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Fermer le rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Rail de navigation"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
index 7ca008b..d09f25a 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Heure"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"en minutes"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"en heures"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Fermer le rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Rail de navigation"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
index 5afbe18..f0183d1 100644
--- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"por minuto"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"por hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Pechar o raíl"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Raíl de navegación"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
index c11bbbb..f364072 100644
--- a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"કલાક"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"મિનિટ માટે"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"કલાક માટે"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"રેલ બંધ કરો"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"નૅવિગેશન રેલ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
index 8e63e86..d7ed08e 100644
--- a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"घंटा"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"मिनट के लिए"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"घंटे के लिए"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"मॉडल रेल को बंद करें"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"नेविगेशन रेल"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
index 7c88f5a..02f88a8 100644
--- a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Sat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"za minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"za sat"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zatvorena pruga"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Pruga za navigaciju"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
index 78d1d69..49e37a3 100644
--- a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Óra"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"perc megadása"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"óra megadása"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Sáv bezárása"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigációs sáv"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
index 8ab9c0a..5cc9626 100644
--- a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ժամ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"րոպեներ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ժամեր"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Փակել նավիգացիայի գոտին"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Նավիգացիայի գոտի"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-in/strings.xml b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
index 468c126..29b22ce 100644
--- a/compose/material3/material3/src/androidMain/res/values-in/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Jam"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"untuk menit"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"untuk jam"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Tutup kolom samping"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Kolom samping navigasi"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-is/strings.xml b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
index aef1730..542d555 100644
--- a/compose/material3/material3/src/androidMain/res/values-is/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Klukkustund"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"fyrir mínútur"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"fyrir klukkustund"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Loka stiku"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Yfirlitsstika"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-it/strings.xml b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
index 03e580f..db4c54d 100644
--- a/compose/material3/material3/src/androidMain/res/values-it/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"per i minuti"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"per l\'ora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Chiudi modalità laterale"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Modalità di navigazione laterale"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
index 3f22dbf..a5a414f 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"שעות"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"דקות"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"שעות"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"סגירה של פס הניווט"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"פס ניווט"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
index c0dd648..de0ca02 100644
--- a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"時間"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"(分単位)"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"(時間単位)"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"レールを閉じる"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ナビゲーション レール"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
index d185bc3..0ec01d0 100644
--- a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"საათი"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"რამდენიმე წუთით"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ერთი საათით"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"არხის დახურვა"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ნავიგაციის არხი"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
index 59e80f0..ae1fab5 100644
--- a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Сағат"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"минут"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"сағат"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Жолақты жабу"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Навигация жолағы"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-km/strings.xml b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
index 26afcd6..c851bc36 100644
--- a/compose/material3/material3/src/androidMain/res/values-km/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ម៉ោង"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"រយៈពេលប៉ុន្មាននាទី"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"រយៈពេលប៉ុន្មានម៉ោង"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"បិទខ្សែ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ខ្សែរុករក"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
index 81d4668..6d1987e 100644
--- a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ಗಂಟೆ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ನಿಮಿಷಗಳವರೆಗೆ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ಗಂಟೆಯವರೆಗೆ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ರೇಲ್‌ ಮುಚ್ಚಿರಿ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ನ್ಯಾವಿಗೇಷನ್ ರೇಲ್‌"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
index 156c846..4546572 100644
--- a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"시간"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"기간(분)"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"기간(시간)"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"레일 닫기"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"탐색 레일"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
index ade5e93..451b078 100644
--- a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Саат"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"мүнөткө"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"саатка"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Капталдагы тилкени жабуу"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Капталдагы өтүү тилкеси"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
index 4c19546..21c6627 100644
--- a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ຊົ່ວໂມງ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ສຳລັບນາທີ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ສຳລັບຊົ່ວໂມງ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ປິດແຖບຂ້າງ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ແຖບຂ້າງສຳລັບນຳທາງໄປຫາສ່ວນຕ່າງໆ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
index c58cca8..3c4b5de 100644
--- a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Valanda"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minutės"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"valandos"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Uždaryti juostą"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Naršymo juosta"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
index 232485d..f951222 100644
--- a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Stunda"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minūtēm"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"stundām"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Aizvērt joslu"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigācijas josla"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
index da1a4fc..c32c957 100644
--- a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Час"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"за минути"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"за час"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Затворање на шината"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Шина за навигација"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
index 8db383f..82d1b89 100644
--- a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"മണിക്കൂർ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"മിനിറ്റ് നേരത്തേക്ക്"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"മണിക്കൂർ നേരത്തേക്ക്"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"റെയിൽ അടയ്ക്കുക"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"നാവിഗേഷൻ റെയിൽ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
index 3f34d54..cc7f970 100644
--- a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Цаг"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"минутын турш"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"цагийн турш"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Хажуугийн навигацын бүрэлдэхүүнийг хаах"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Хажуугийн навигацын бүрэлдэхүүн"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
index adec551..6d3b344 100644
--- a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"तास"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"मिनिटांसाठी"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"तासासाठी"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"रेल बंद करा"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"नेव्हिगेशन रेल"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
index 8d0aa69..993981d1 100644
--- a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Jam"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"selama # minit"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"selama # jam"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Tutup rel"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Rel navigasi"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-my/strings.xml b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
index b6c7778..515f884 100644
--- a/compose/material3/material3/src/androidMain/res/values-my/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"နာရီ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"မိနစ်ကြာ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"နာရီကြာ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ဘားပိတ်ရန်"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"လမ်းကြောင်းပြဘား"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
index 9940cb9..59c3a50 100644
--- a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Time"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"for minutter"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"for timer"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Lukk linjen"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigasjonslinje"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
index c6d4072..662c1a1 100644
--- a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"घण्टा"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"मिनेटका लागि"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"घण्टाका लागि"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"रेल बन्द गर्नुहोस्"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"नेभिगेसन रेल"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
index b203904..3631688 100644
--- a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Uur"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"voor minuten"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"voor uur"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Rail sluiten"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigatierail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-or/strings.xml b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
index 16c77d94..fafe884 100644
--- a/compose/material3/material3/src/androidMain/res/values-or/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ଘଣ୍ଟା"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ମିନିଟ ପାଇଁ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ଘଣ୍ଟା ପାଇଁ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ରେଲ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ନାଭିଗେସନ ରେଲ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
index 8714702..8a7e60c 100644
--- a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ਘੰਟਾ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ਮਿੰਟਾਂ ਲਈ"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ਘੰਟੇ ਲਈ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ਰੇਲ ਬੰਦ ਕਰੋ"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"ਨੈਵੀਗੇਸ਼ਨ ਰੇਲ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
index a5ef6fb..63acf27 100644
--- a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Godzina"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"aby wpisać minuty"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"aby wpisać godzinę"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zamknij kolumnę"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Kolumna nawigacji"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
index 2b0542e..6d3ffd2 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"por minutos"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"por hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Fechar coluna"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Coluna de navegação"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
index d4f841b..db16e83 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"para minutos"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"para hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Fechar barra"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Barra de navegação"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
index 2b0542e..6d3ffd2 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"por minutos"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"por hora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Fechar coluna"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Coluna de navegação"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
index 2151f2f..f4f017e 100644
--- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Oră"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"pentru minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"pentru oră"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Închide bara"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Bară de navigare"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
index bf114c6..6ca3737 100644
--- a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Часы"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"минуты"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"часы"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Закрыть боковую панель"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Боковая панель навигации"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-si/strings.xml b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
index 52b5bf1..03da10b 100644
--- a/compose/material3/material3/src/androidMain/res/values-si/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"පැය"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"මිනිත්තු ගණනක් සඳහා"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"පැයක් සඳහා"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"පීල්ල වසන්න"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"සංචාලන පීල්ල"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
index 1c487969..6bed626 100644
--- a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Hodina"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"minúty"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"hodiny"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zavrieť pruh"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigačný pruh"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
index 659710d..2bbdcf8 100644
--- a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ura"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"za minute"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"za uro"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Zapiranje črte"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Črta za krmarjenje"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
index 4b522cf..7f3651d 100644
--- a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"për minuta"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"për orë"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Mbyll shinën"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Shina e shfletimit"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
index 2f57857..fc7c5ed 100644
--- a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Сат"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"за минуте"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"за сате"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Затворите траку"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Трака за навигацију"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
index 34387b3..531e9f3 100644
--- a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Timme"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"för minuter"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"för timme"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Stäng spår"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigeringsspår"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
index dd3938b..7414544 100644
--- a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Saa"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"cha dakika"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"cha moja"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Funga reli ya usogezaji"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Reli ya usogezaji"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
index 79690da..30bc75a 100644
--- a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"மணிநேரம்"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"நிமிடங்களுக்கு"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"மணிநேரத்திற்கு"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"பாதையை மூடும்"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"வழிசெலுத்தல் பாதை"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-te/strings.xml b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
index a0def42..23e2d07 100644
--- a/compose/material3/material3/src/androidMain/res/values-te/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"గంట"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"నిమిషాలను ఎంచుకోవడం కోసం"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"గంటలను ఎంచుకోవడం కోసం"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"రైలును మూసివేయండి"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"నావిగేషన్ రైలు"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-th/strings.xml b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
index 87a137d..6bbddd7 100644
--- a/compose/material3/material3/src/androidMain/res/values-th/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"ชั่วโมง"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"สำหรับนาที"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"สำหรับชั่วโมง"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ปิดแถบข้าง"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"แถบข้างสำหรับไปยังส่วนต่างๆ"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
index b0843deb..5e3b166 100644
--- a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Oras"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"nang ilang minuto"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"nang ilang oras"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Isara ang rail"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
index 56f3ff9..10c2af6 100644
--- a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Saat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"dakika"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"saat"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Sütunu kapat"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Gezinme sütunu"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
index be40917..f39dd4d 100644
--- a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Година"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"для хвилин"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"для годин"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Закрити панель"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Панель навігації"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
index 752366d..981ea58 100644
--- a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"گھنٹہ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"منٹ کے لیے"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"گھنٹے کے لیے"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"ریل بند کریں"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"نیوی گیشن ریل"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
index 00e7d12..f996d98 100644
--- a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Soat"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"bir daqiqa"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"bir soat"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Panelni yopish"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Navigatsiya paneli"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
index f980dc4..812362c 100644
--- a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Giờ"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"nhập phút"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"nhập giờ"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Đóng dải điều hướng"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Dải điều hướng"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
index 6ca9d53..29a48ba 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"小时"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"输入分钟"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"输入小时"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"关闭此栏"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"侧边导航栏"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
index 6136555..c18a5a3 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"小時"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"分鐘"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"小時"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"閂咗個欄"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"導覽欄"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
index 9b315f6..48024e6 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"小時"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"分鐘"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"小時"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"關閉邊欄"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"導覽邊欄"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
index 8ed709b..7f093ac 100644
--- a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
@@ -75,4 +75,6 @@
     <string name="m3c_time_picker_hour" msgid="2349193472625211372">"Ihora"</string>
     <string name="m3c_time_picker_minute_text_field" msgid="7661234488295443182">"ngemizuzu"</string>
     <string name="m3c_time_picker_hour_text_field" msgid="6973808109666874069">"ngehora"</string>
+    <string name="m3c_wide_navigation_rail_close_rail" msgid="6564222473543418073">"Vala ikhalamu lezikhangiso"</string>
+    <string name="m3c_wide_navigation_rail_pane_title" msgid="8350283435461615411">"Ikhalamu lezikhangiso lokufuna"</string>
 </resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
index 0fc77f6..e3f0cc8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.updateTransition
@@ -56,18 +57,33 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.CacheDrawModifierNode
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawscope.inset
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toIntSize
+import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.util.lerp
+import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
 /**
@@ -176,6 +192,10 @@
  * image](https://developer.android.com/images/reference/androidx/compose/material3/small-fab.png)
  *
  * @sample androidx.compose.material3.samples.SmallFloatingActionButtonSample
+ *
+ * FABs can also be shown and hidden with an animation when the main content is scrolled:
+ *
+ * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
  * @param onClick called when this FAB is clicked
  * @param modifier the [Modifier] to be applied to this FAB
  * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
@@ -228,6 +248,10 @@
  * The FAB represents the most important action on a screen. It puts key actions within reach.
  *
  * @sample androidx.compose.material3.samples.MediumFloatingActionButtonSample
+ *
+ * FABs can also be shown and hidden with an animation when the main content is scrolled:
+ *
+ * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
  * @param onClick called when this FAB is clicked
  * @param modifier the [Modifier] to be applied to this FAB
  * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
@@ -285,6 +309,10 @@
  * image](https://developer.android.com/images/reference/androidx/compose/material3/large-fab.png)
  *
  * @sample androidx.compose.material3.samples.LargeFloatingActionButtonSample
+ *
+ * FABs can also be shown and hidden with an animation when the main content is scrolled:
+ *
+ * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
  * @param onClick called when this FAB is clicked
  * @param modifier the [Modifier] to be applied to this FAB
  * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
@@ -944,6 +972,8 @@
 
 /** Contains the default values used by [FloatingActionButton] */
 object FloatingActionButtonDefaults {
+    internal val ShowHideTargetScale = 0.2f
+
     /** The recommended size of the icon inside a [MediumFloatingActionButton]. */
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
@@ -1075,6 +1105,148 @@
 }
 
 /**
+ * Apply this modifier to a [FloatingActionButton] to show or hide it with an animation, typically
+ * based on the app's main content scrolling.
+ *
+ * @param visible whether the FAB should be shown or hidden with an animation
+ * @param alignment the direction towards which the FAB should be scaled to and from
+ * @param targetScale the initial scale value when showing the FAB and the final scale value when
+ *   hiding the FAB
+ * @param scaleAnimationSpec the [AnimationSpec] to use for the scale part of the animation, if null
+ *   the Fast Spatial spring spec from the [MotionScheme] will be used
+ * @param alphaAnimationSpec the [AnimationSpec] to use for the alpha part of the animation, if null
+ *   the Fast Effects spring spec from the [MotionScheme] will be used
+ * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
+ */
+@ExperimentalMaterial3ExpressiveApi
+fun Modifier.animateFloatingActionButton(
+    visible: Boolean,
+    alignment: Alignment,
+    targetScale: Float = FloatingActionButtonDefaults.ShowHideTargetScale,
+    scaleAnimationSpec: AnimationSpec<Float>? = null,
+    alphaAnimationSpec: AnimationSpec<Float>? = null
+): Modifier {
+    return this.then(
+        FabVisibleModifier(
+            visible = visible,
+            alignment = alignment,
+            targetScale = targetScale,
+            scaleAnimationSpec = scaleAnimationSpec,
+            alphaAnimationSpec = alphaAnimationSpec
+        )
+    )
+}
+
+internal data class FabVisibleModifier(
+    private val visible: Boolean,
+    private val alignment: Alignment,
+    private val targetScale: Float,
+    private val scaleAnimationSpec: AnimationSpec<Float>? = null,
+    private val alphaAnimationSpec: AnimationSpec<Float>? = null
+) : ModifierNodeElement<FabVisibleNode>() {
+
+    override fun create(): FabVisibleNode =
+        FabVisibleNode(
+            visible = visible,
+            alignment = alignment,
+            targetScale = targetScale,
+            scaleAnimationSpec = scaleAnimationSpec,
+            alphaAnimationSpec = alphaAnimationSpec,
+        )
+
+    override fun update(node: FabVisibleNode) {
+        node.updateNode(
+            visible = visible,
+            alignment = alignment,
+            targetScale = targetScale,
+            scaleAnimationSpec = scaleAnimationSpec,
+            alphaAnimationSpec = alphaAnimationSpec,
+        )
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // Show nothing in the inspector.
+    }
+}
+
+internal class FabVisibleNode(
+    visible: Boolean,
+    private var alignment: Alignment,
+    private var targetScale: Float,
+    private var scaleAnimationSpec: AnimationSpec<Float>? = null,
+    private var alphaAnimationSpec: AnimationSpec<Float>? = null,
+) : DelegatingNode(), CompositionLocalConsumerModifierNode {
+
+    private val scaleAnimatable = Animatable(if (visible) 1f else 0f)
+    private val alphaAnimatable = Animatable(if (visible) 1f else 0f)
+
+    val delegate =
+        delegate(
+            CacheDrawModifierNode {
+                val layer = obtainGraphicsLayer()
+                // Use a larger layer size to make sure the elevation shadow doesn't get clipped
+                // and offset via layer.topLeft and DrawScope.inset to preserve the visual
+                // position of the FAB.
+                val layerInsetSize = 16.dp.toPx()
+                val layerSize =
+                    Size(size.width + layerInsetSize * 2f, size.height + layerInsetSize * 2f)
+                        .toIntSize()
+                val nodeSize = size.toIntSize()
+
+                layer.apply {
+                    topLeft = IntOffset(-layerInsetSize.roundToInt(), -layerInsetSize.roundToInt())
+
+                    alpha = alphaAnimatable.value
+
+                    // Scale towards the direction of the provided alignment
+                    val alignOffset = alignment.align(IntSize(1, 1), nodeSize, layoutDirection)
+                    pivotOffset = alignOffset.toOffset() + Offset(layerInsetSize, layerInsetSize)
+                    scaleX = lerp(targetScale, 1f, scaleAnimatable.value)
+                    scaleY = lerp(targetScale, 1f, scaleAnimatable.value)
+
+                    record(size = layerSize) {
+                        inset(layerInsetSize, layerInsetSize) { [email protected]() }
+                    }
+                }
+
+                onDrawWithContent { drawLayer(layer) }
+            }
+        )
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    fun updateNode(
+        visible: Boolean,
+        alignment: Alignment,
+        targetScale: Float,
+        scaleAnimationSpec: AnimationSpec<Float>?,
+        alphaAnimationSpec: AnimationSpec<Float>?
+    ) {
+        this.alignment = alignment
+        this.targetScale = targetScale
+        this.scaleAnimationSpec = scaleAnimationSpec
+        this.alphaAnimationSpec = alphaAnimationSpec
+
+        coroutineScope.launch {
+            // TODO Load the motionScheme tokens from the component tokens file
+            scaleAnimatable.animateTo(
+                targetValue = if (visible) 1f else 0f,
+                animationSpec =
+                    scaleAnimationSpec ?: currentValueOf(LocalMotionScheme).fastSpatialSpec()
+            )
+        }
+
+        coroutineScope.launch {
+            // TODO Load the motionScheme tokens from the component tokens file
+            alphaAnimatable.animateTo(
+                targetValue = if (visible) 1f else 0f,
+                animationSpec =
+                    alphaAnimationSpec ?: currentValueOf(LocalMotionScheme).fastEffectsSpec()
+            )
+        }
+    }
+}
+
+/**
  * Represents the tonal and shadow elevation for a floating action button in different states.
  *
  * See [FloatingActionButtonDefaults.elevation] for the default elevation used in a
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
index 439bf25..794b6b6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
@@ -78,7 +78,8 @@
 import kotlin.math.roundToInt
 
 /**
- * @sample androidx.compose.material3.samples.HorizontalFloatingAppBar
+ * @sample androidx.compose.material3.samples.ExpandableHorizontalFloatingAppBar
+ * @sample androidx.compose.material3.samples.ScrollableHorizontalFloatingAppBar
  * @param expanded whether the FloatingAppBar is in expanded mode, i.e. showing [leadingContent] and
  *   [trailingContent].
  * @param modifier the [Modifier] to be applied to this FloatingAppBar.
@@ -146,7 +147,8 @@
 }
 
 /**
- * @sample androidx.compose.material3.samples.VerticalFloatingAppBar
+ * @sample androidx.compose.material3.samples.ExpandableVerticalFloatingAppBar
+ * @sample androidx.compose.material3.samples.ScrollableVerticalFloatingAppBar
  * @param expanded whether the FloatingAppBar is in expanded mode, i.e. showing [leadingContent] and
  *   [trailingContent].
  * @param modifier the [Modifier] to be applied to this FloatingAppBar.
@@ -422,7 +424,7 @@
     @Composable
     fun horizontalEnterTransition(expandFrom: Alignment.Horizontal) =
         expandHorizontally(
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value(),
+            animationSpec = MotionSchemeKeyTokens.FastSpatial.value(),
             expandFrom = expandFrom,
         )
 
@@ -430,7 +432,7 @@
     @Composable
     fun verticalEnterTransition(expandFrom: Alignment.Vertical) =
         expandVertically(
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value(),
+            animationSpec = MotionSchemeKeyTokens.FastSpatial.value(),
             expandFrom = expandFrom,
         )
 
@@ -438,7 +440,7 @@
     @Composable
     fun horizontalExitTransition(shrinkTowards: Alignment.Horizontal) =
         shrinkHorizontally(
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value(),
+            animationSpec = MotionSchemeKeyTokens.FastSpatial.value(),
             shrinkTowards = shrinkTowards,
         )
 
@@ -446,7 +448,7 @@
     @Composable
     fun verticalExitTransition(shrinkTowards: Alignment.Vertical) =
         shrinkVertically(
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value(),
+            animationSpec = MotionSchemeKeyTokens.FastSpatial.value(),
             shrinkTowards = shrinkTowards,
         )
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
index e144c31..d1ac45a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
@@ -64,9 +64,11 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.dismiss
 import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.traversalIndex
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
@@ -176,11 +178,11 @@
         },
         predictiveBackProgress = predictiveBackProgress,
     ) {
-        Box(modifier = Modifier.fillMaxSize().imePadding()) {
+        Box(modifier = Modifier.fillMaxSize().imePadding().semantics { isTraversalGroup = true }) {
             Scrim(
                 color = scrimColor,
                 onDismissRequest = animateToDismiss,
-                visible = sheetState.targetValue != Hidden
+                visible = sheetState.targetValue != Hidden,
             )
             ModalBottomSheetContent(
                 predictiveBackProgress,
@@ -277,7 +279,10 @@
                     startDragImmediately = sheetState.anchoredDraggableState.isAnimationRunning,
                     onDragStopped = { settleToDismiss(it) }
                 )
-                .semantics { paneTitle = bottomSheetPaneTitle }
+                .semantics {
+                    paneTitle = bottomSheetPaneTitle
+                    traversalIndex = 0f
+                }
                 .graphicsLayer {
                     val sheetOffset = sheetState.anchoredDraggableState.offset
                     val sheetHeight = size.height
@@ -442,6 +447,7 @@
             if (visible) {
                 Modifier.pointerInput(onDismissRequest) { detectTapGestures { onDismissRequest() } }
                     .semantics(mergeDescendants = true) {
+                        traversalIndex = 1f
                         contentDescription = closeSheet
                         onClick {
                             onDismissRequest()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
index d1f3576..0a5aec8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
@@ -41,6 +41,7 @@
 import androidx.compose.material3.tokens.ElevationTokens
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
 import androidx.compose.material3.tokens.NavigationBarTokens
+import androidx.compose.material3.tokens.NavigationBarVerticalItemTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -250,7 +251,7 @@
         // the indicator.
         val deltaOffset: Offset
         with(LocalDensity.current) {
-            val indicatorWidth = NavigationBarTokens.ActiveIndicatorWidth.roundToPx()
+            val indicatorWidth = NavigationBarVerticalItemTokens.ActiveIndicatorWidth.roundToPx()
             deltaOffset =
                 Offset((itemWidth - indicatorWidth).toFloat() / 2, IndicatorVerticalOffset.toPx())
         }
@@ -265,7 +266,7 @@
             @Composable {
                 Box(
                     Modifier.layoutId(IndicatorRippleLayoutIdTag)
-                        .clip(NavigationBarTokens.ActiveIndicatorShape.value)
+                        .clip(NavigationBarTokens.ItemActiveIndicatorShape.value)
                         .indication(offsetInteractionSource, ripple())
                 )
             }
@@ -276,7 +277,7 @@
                         .graphicsLayer { alpha = alphaAnimationProgress.value }
                         .background(
                             color = colors.indicatorColor,
-                            shape = NavigationBarTokens.ActiveIndicatorShape.value,
+                            shape = NavigationBarTokens.ItemActiveIndicatorShape.value,
                         )
                 )
             }
@@ -357,17 +358,18 @@
         get() {
             return defaultNavigationBarItemColorsCached
                 ?: NavigationBarItemColors(
-                        selectedIconColor = fromToken(NavigationBarTokens.ActiveIconColor),
-                        selectedTextColor = fromToken(NavigationBarTokens.ActiveLabelTextColor),
+                        selectedIconColor = fromToken(NavigationBarTokens.ItemActiveIconColor),
+                        selectedTextColor = fromToken(NavigationBarTokens.ItemActiveLabelTextColor),
                         selectedIndicatorColor =
-                            fromToken(NavigationBarTokens.ActiveIndicatorColor),
-                        unselectedIconColor = fromToken(NavigationBarTokens.InactiveIconColor),
-                        unselectedTextColor = fromToken(NavigationBarTokens.InactiveLabelTextColor),
+                            fromToken(NavigationBarTokens.ItemActiveIndicatorColor),
+                        unselectedIconColor = fromToken(NavigationBarTokens.ItemInactiveIconColor),
+                        unselectedTextColor =
+                            fromToken(NavigationBarTokens.ItemInactiveLabelTextColor),
                         disabledIconColor =
-                            fromToken(NavigationBarTokens.InactiveIconColor)
+                            fromToken(NavigationBarTokens.ItemInactiveIconColor)
                                 .copy(alpha = DisabledAlpha),
                         disabledTextColor =
-                            fromToken(NavigationBarTokens.InactiveLabelTextColor)
+                            fromToken(NavigationBarTokens.ItemInactiveLabelTextColor)
                                 .copy(alpha = DisabledAlpha),
                     )
                     .also { defaultNavigationBarItemColorsCached = it }
@@ -379,11 +381,11 @@
     )
     @Composable
     fun colors(
-        selectedIconColor: Color = NavigationBarTokens.ActiveIconColor.value,
-        selectedTextColor: Color = NavigationBarTokens.ActiveLabelTextColor.value,
-        indicatorColor: Color = NavigationBarTokens.ActiveIndicatorColor.value,
-        unselectedIconColor: Color = NavigationBarTokens.InactiveIconColor.value,
-        unselectedTextColor: Color = NavigationBarTokens.InactiveLabelTextColor.value,
+        selectedIconColor: Color = NavigationBarTokens.ItemActiveIconColor.value,
+        selectedTextColor: Color = NavigationBarTokens.ItemActiveLabelTextColor.value,
+        indicatorColor: Color = NavigationBarTokens.ItemActiveIndicatorColor.value,
+        unselectedIconColor: Color = NavigationBarTokens.ItemInactiveIconColor.value,
+        unselectedTextColor: Color = NavigationBarTokens.ItemInactiveLabelTextColor.value,
     ): NavigationBarItemColors =
         NavigationBarItemColors(
             selectedIconColor = selectedIconColor,
@@ -716,7 +718,7 @@
 
 private const val LabelLayoutIdTag: String = "label"
 
-private val NavigationBarHeight: Dp = NavigationBarTokens.ContainerHeight
+private val NavigationBarHeight: Dp = NavigationBarTokens.TallContainerHeight
 
 /*@VisibleForTesting*/
 internal val NavigationBarItemHorizontalPadding: Dp = 8.dp
@@ -725,10 +727,12 @@
 internal val NavigationBarIndicatorToLabelPadding: Dp = 4.dp
 
 private val IndicatorHorizontalPadding: Dp =
-    (NavigationBarTokens.ActiveIndicatorWidth - NavigationBarTokens.IconSize) / 2
+    (NavigationBarVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationBarVerticalItemTokens.IconSize) / 2
 
 /*@VisibleForTesting*/
 internal val IndicatorVerticalPadding: Dp =
-    (NavigationBarTokens.ActiveIndicatorHeight - NavigationBarTokens.IconSize) / 2
+    (NavigationBarVerticalItemTokens.ActiveIndicatorHeight -
+        NavigationBarVerticalItemTokens.IconSize) / 2
 
 private val IndicatorVerticalOffset: Dp = 12.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 76bdd73..771d263 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -306,7 +306,7 @@
             icon = iconWithBadge,
             iconPosition = iconPosition,
             label = styledLabel,
-            indicatorAnimationProgress = { indicatorAnimationProgress.value },
+            indicatorAnimationProgress = { indicatorAnimationProgress.value.coerceAtLeast(0f) },
             indicatorHorizontalPadding = indicatorHorizontalPadding,
             indicatorVerticalPadding = indicatorVerticalPadding,
             indicatorToLabelVerticalPadding = indicatorToLabelVerticalPadding,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
index 4caab04..c88ebfef 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
@@ -42,7 +42,11 @@
 import androidx.compose.material3.internal.ProvideContentColorTextStyle
 import androidx.compose.material3.internal.systemBarsForVisualComponents
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
-import androidx.compose.material3.tokens.NavigationRailTokens
+import androidx.compose.material3.tokens.NavigationRailBaselineItemTokens
+import androidx.compose.material3.tokens.NavigationRailCollapsedTokens
+import androidx.compose.material3.tokens.NavigationRailColorTokens
+import androidx.compose.material3.tokens.NavigationRailVerticalItemTokens
+import androidx.compose.material3.tokens.ShapeKeyTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -125,7 +129,7 @@
         Column(
             Modifier.fillMaxHeight()
                 .windowInsetsPadding(windowInsets)
-                .widthIn(min = NavigationRailTokens.ContainerWidth)
+                .widthIn(min = NavigationRailCollapsedTokens.NarrowContainerWidth)
                 .padding(vertical = NavigationRailVerticalPadding)
                 .selectableGroup(),
             horizontalAlignment = Alignment.CenterHorizontally,
@@ -201,7 +205,7 @@
     val styledLabel: @Composable (() -> Unit)? =
         label?.let {
             @Composable {
-                val style = NavigationRailTokens.LabelTextFont.value
+                val style = NavigationRailVerticalItemTokens.LabelTextFont.value
                 val textColor by
                     animateColorAsState(
                         targetValue = colors.textColor(selected = selected, enabled = enabled),
@@ -249,7 +253,7 @@
         val deltaOffset: Offset
         with(LocalDensity.current) {
             val itemWidth = NavigationRailItemWidth.roundToPx()
-            val indicatorWidth = NavigationRailTokens.ActiveIndicatorWidth.roundToPx()
+            val indicatorWidth = NavigationRailVerticalItemTokens.ActiveIndicatorWidth.roundToPx()
             deltaOffset = Offset((itemWidth - indicatorWidth).toFloat() / 2, 0f)
         }
         val offsetInteractionSource =
@@ -259,9 +263,9 @@
 
         val indicatorShape =
             if (label != null) {
-                NavigationRailTokens.ActiveIndicatorShape.value
+                NavigationRailBaselineItemTokens.ActiveIndicatorShape.value
             } else {
-                NavigationRailTokens.NoLabelActiveIndicatorShape.value
+                ShapeKeyTokens.CornerFull.value
             }
 
         // The indicator has a width-expansion animation which interferes with the timing of the
@@ -299,7 +303,7 @@
 object NavigationRailDefaults {
     /** Default container color of a navigation rail. */
     val ContainerColor: Color
-        @Composable get() = NavigationRailTokens.ContainerColor.value
+        @Composable get() = NavigationRailCollapsedTokens.ContainerColor.value
 
     /** Default window insets for navigation rail. */
     val windowInsets: WindowInsets
@@ -333,11 +337,11 @@
      */
     @Composable
     fun colors(
-        selectedIconColor: Color = NavigationRailTokens.ActiveIconColor.value,
-        selectedTextColor: Color = NavigationRailTokens.ActiveLabelTextColor.value,
-        indicatorColor: Color = NavigationRailTokens.ActiveIndicatorColor.value,
-        unselectedIconColor: Color = NavigationRailTokens.InactiveIconColor.value,
-        unselectedTextColor: Color = NavigationRailTokens.InactiveLabelTextColor.value,
+        selectedIconColor: Color = NavigationRailColorTokens.ItemActiveIcon.value,
+        selectedTextColor: Color = NavigationRailColorTokens.ItemActiveLabelText.value,
+        indicatorColor: Color = NavigationRailColorTokens.ItemActiveIndicator.value,
+        unselectedIconColor: Color = NavigationRailColorTokens.ItemInactiveIcon.value,
+        unselectedTextColor: Color = NavigationRailColorTokens.ItemInactiveLabelText.value,
         disabledIconColor: Color = unselectedIconColor.copy(alpha = DisabledAlpha),
         disabledTextColor: Color = unselectedTextColor.copy(alpha = DisabledAlpha),
     ): NavigationRailItemColors =
@@ -355,18 +359,19 @@
         get() {
             return defaultNavigationRailItemColorsCached
                 ?: NavigationRailItemColors(
-                        selectedIconColor = fromToken(NavigationRailTokens.ActiveIconColor),
-                        selectedTextColor = fromToken(NavigationRailTokens.ActiveLabelTextColor),
+                        selectedIconColor = fromToken(NavigationRailColorTokens.ItemActiveIcon),
+                        selectedTextColor =
+                            fromToken(NavigationRailColorTokens.ItemActiveLabelText),
                         selectedIndicatorColor =
-                            fromToken(NavigationRailTokens.ActiveIndicatorColor),
-                        unselectedIconColor = fromToken(NavigationRailTokens.InactiveIconColor),
+                            fromToken(NavigationRailColorTokens.ItemActiveIndicator),
+                        unselectedIconColor = fromToken(NavigationRailColorTokens.ItemInactiveIcon),
                         unselectedTextColor =
-                            fromToken(NavigationRailTokens.InactiveLabelTextColor),
+                            fromToken(NavigationRailColorTokens.ItemInactiveLabelText),
                         disabledIconColor =
-                            fromToken(NavigationRailTokens.InactiveIconColor)
+                            fromToken(NavigationRailColorTokens.ItemInactiveIcon)
                                 .copy(alpha = DisabledAlpha),
                         disabledTextColor =
-                            fromToken(NavigationRailTokens.InactiveLabelTextColor)
+                            fromToken(NavigationRailColorTokens.ItemInactiveLabelText)
                                 .copy(alpha = DisabledAlpha),
                     )
                     .also { defaultNavigationRailItemColorsCached = it }
@@ -378,11 +383,11 @@
     )
     @Composable
     fun colors(
-        selectedIconColor: Color = NavigationRailTokens.ActiveIconColor.value,
-        selectedTextColor: Color = NavigationRailTokens.ActiveLabelTextColor.value,
-        indicatorColor: Color = NavigationRailTokens.ActiveIndicatorColor.value,
-        unselectedIconColor: Color = NavigationRailTokens.InactiveIconColor.value,
-        unselectedTextColor: Color = NavigationRailTokens.InactiveLabelTextColor.value,
+        selectedIconColor: Color = NavigationRailColorTokens.ItemActiveIcon.value,
+        selectedTextColor: Color = NavigationRailColorTokens.ItemActiveLabelText.value,
+        indicatorColor: Color = NavigationRailColorTokens.ItemActiveIndicator.value,
+        unselectedIconColor: Color = NavigationRailColorTokens.ItemInactiveIcon.value,
+        unselectedTextColor: Color = NavigationRailColorTokens.ItemInactiveLabelText.value,
     ): NavigationRailItemColors =
         NavigationRailItemColors(
             selectedIconColor = selectedIconColor,
@@ -745,21 +750,24 @@
 
 /*@VisibleForTesting*/
 /** Width of an individual [NavigationRailItem]. */
-internal val NavigationRailItemWidth: Dp = NavigationRailTokens.ContainerWidth
+internal val NavigationRailItemWidth: Dp = NavigationRailCollapsedTokens.NarrowContainerWidth
 
 /*@VisibleForTesting*/
 /** Height of an individual [NavigationRailItem]. */
-internal val NavigationRailItemHeight: Dp = NavigationRailTokens.NoLabelActiveIndicatorHeight
+internal val NavigationRailItemHeight: Dp = NavigationRailVerticalItemTokens.ActiveIndicatorWidth
 
 /*@VisibleForTesting*/
 /** Vertical padding between the contents of a [NavigationRailItem] and its top/bottom. */
 internal val NavigationRailItemVerticalPadding: Dp = 4.dp
 
 private val IndicatorHorizontalPadding: Dp =
-    (NavigationRailTokens.ActiveIndicatorWidth - NavigationRailTokens.IconSize) / 2
+    (NavigationRailVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationRailBaselineItemTokens.IconSize) / 2
 
 private val IndicatorVerticalPaddingWithLabel: Dp =
-    (NavigationRailTokens.ActiveIndicatorHeight - NavigationRailTokens.IconSize) / 2
+    (NavigationRailVerticalItemTokens.ActiveIndicatorHeight -
+        NavigationRailBaselineItemTokens.IconSize) / 2
 
 private val IndicatorVerticalPaddingNoLabel: Dp =
-    (NavigationRailTokens.NoLabelActiveIndicatorHeight - NavigationRailTokens.IconSize) / 2
+    (NavigationRailVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationRailBaselineItemTokens.IconSize) / 2
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
index 23fed45..54aa142 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
@@ -25,9 +25,9 @@
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material3.internal.systemBarsForVisualComponents
-import androidx.compose.material3.tokens.ColorSchemeKeyTokens
-import androidx.compose.material3.tokens.ShapeKeyTokens
-import androidx.compose.material3.tokens.TypographyKeyTokens
+import androidx.compose.material3.tokens.NavigationBarHorizontalItemTokens
+import androidx.compose.material3.tokens.NavigationBarTokens
+import androidx.compose.material3.tokens.NavigationBarVerticalItemTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
@@ -101,7 +101,7 @@
             modifier =
                 modifier
                     .windowInsetsPadding(windowInsets)
-                    .defaultMinSize(minHeight = NavigationBarHeight)
+                    .defaultMinSize(minHeight = NavigationBarTokens.ContainerHeight)
                     .selectableGroup(),
             content = content,
             measurePolicy =
@@ -168,11 +168,11 @@
  * @param selected whether this item is selected
  * @param onClick called when this item is clicked
  * @param icon icon for this item, typically an [Icon]
+ * @param label text label for this item
  * @param modifier the [Modifier] to be applied to this item
  * @param enabled controls the enabled state of this item. When `false`, this component will not
  *   respond to user input, and it will appear visually disabled and disabled to accessibility
  *   services.
- * @param label text label for this item
  * @param badge optional badge to show on this item, typically a [Badge]
  * @param iconPosition the [NavigationItemIconPosition] for the icon
  * @param colors [NavigationItemColors] that will be used to resolve the colors used for this item
@@ -188,9 +188,9 @@
     selected: Boolean,
     onClick: () -> Unit,
     icon: @Composable () -> Unit,
+    label: @Composable (() -> Unit)?,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    label: @Composable (() -> Unit)? = null,
     badge: (@Composable () -> Unit)? = null,
     iconPosition: NavigationItemIconPosition = NavigationItemIconPosition.Top,
     colors: NavigationItemColors = ShortNavigationBarItemDefaults.colors(),
@@ -217,9 +217,9 @@
         selected = selected,
         onClick = onClick,
         icon = icon,
-        labelTextStyle = LabelTextFont.value,
-        indicatorShape = ActiveIndicatorShape.value,
-        indicatorWidth = TopIconItemActiveIndicatorWidth,
+        labelTextStyle = NavigationBarTokens.LabelTextFont.value,
+        indicatorShape = NavigationBarTokens.ItemActiveIndicatorShape.value,
+        indicatorWidth = NavigationBarVerticalItemTokens.ActiveIndicatorWidth,
         indicatorHorizontalPadding = indicatorHorizontalPadding,
         indicatorVerticalPadding = indicatorVerticalPadding,
         indicatorToLabelVerticalPadding = TopIconIndicatorToLabelPadding,
@@ -239,14 +239,12 @@
 @ExperimentalMaterial3ExpressiveApi
 object ShortNavigationBarDefaults {
     /** Default container color for a short navigation bar. */
-    // TODO: Replace with token.
     val containerColor: Color
-        @Composable get() = ColorSchemeKeyTokens.SurfaceContainer.value
+        @Composable get() = NavigationBarTokens.ContainerColor.value
 
     /** Default content color for a short navigation bar. */
-    // TODO: Replace with token.
     val contentColor: Color
-        @Composable get() = ColorSchemeKeyTokens.OnSurfaceVariant.value
+        @Composable get() = contentColorFor(containerColor)
 
     /** Default arrangement for a short navigation bar. */
     val arrangement: ShortNavigationBarArrangement
@@ -274,15 +272,19 @@
         get() {
             return defaultShortNavigationBarItemColorsCached
                 ?: NavigationItemColors(
-                        selectedIconColor = fromToken(ActiveIconColor),
-                        selectedTextColor = fromToken(ActiveLabelTextColor),
-                        selectedIndicatorColor = fromToken(ActiveIndicatorColor),
-                        unselectedIconColor = fromToken(InactiveIconColor),
-                        unselectedTextColor = fromToken(InactiveLabelTextColor),
+                        selectedIconColor = fromToken(NavigationBarTokens.ItemActiveIconColor),
+                        selectedTextColor = fromToken(NavigationBarTokens.ItemActiveLabelTextColor),
+                        selectedIndicatorColor =
+                            fromToken(NavigationBarTokens.ItemActiveIndicatorColor),
+                        unselectedIconColor = fromToken(NavigationBarTokens.ItemInactiveIconColor),
+                        unselectedTextColor =
+                            fromToken(NavigationBarTokens.ItemInactiveLabelTextColor),
                         disabledIconColor =
-                            fromToken(InactiveIconColor).copy(alpha = DisabledAlpha),
+                            fromToken(NavigationBarTokens.ItemInactiveIconColor)
+                                .copy(alpha = DisabledAlpha),
                         disabledTextColor =
-                            fromToken(InactiveLabelTextColor).copy(alpha = DisabledAlpha),
+                            fromToken(NavigationBarTokens.ItemInactiveLabelTextColor)
+                                .copy(alpha = DisabledAlpha),
                     )
                     .also { defaultShortNavigationBarItemColorsCached = it }
         }
@@ -424,33 +426,24 @@
     return (paddingPercentage * barWidth).roundToInt()
 }
 
-/* TODO: Replace below values with tokens. */
-private val IconSize = 24.0.dp
-private val TopIconItemActiveIndicatorWidth = 56.dp
-private val TopIconItemActiveIndicatorHeight = 32.dp
-private val StartIconItemActiveIndicatorHeight = 40.dp
-private val LabelTextFont = TypographyKeyTokens.LabelMedium
-private val ActiveIndicatorShape = ShapeKeyTokens.CornerFull
-// TODO: Update to OnSecondaryContainer once value matches Secondary.
-private val ActiveIconColor = ColorSchemeKeyTokens.Secondary
-// TODO: Update to OnSecondaryContainer once value matches Secondary.
-private val ActiveLabelTextColor = ColorSchemeKeyTokens.Secondary
-private val ActiveIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
-private val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
-private val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
-private val NavigationBarHeight = 64.dp
-
 /*@VisibleForTesting*/
-internal val TopIconItemVerticalPadding = 6.dp
+internal val TopIconItemVerticalPadding = NavigationBarVerticalItemTokens.ContainerBetweenSpace
 /*@VisibleForTesting*/
-internal val TopIconIndicatorVerticalPadding = (TopIconItemActiveIndicatorHeight - IconSize) / 2
+internal val TopIconIndicatorVerticalPadding =
+    (NavigationBarVerticalItemTokens.ActiveIndicatorHeight -
+        NavigationBarVerticalItemTokens.IconSize) / 2
 /*@VisibleForTesting*/
-internal val TopIconIndicatorHorizontalPadding = (TopIconItemActiveIndicatorWidth - IconSize) / 2
+internal val TopIconIndicatorHorizontalPadding =
+    (NavigationBarVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationBarVerticalItemTokens.IconSize) / 2
 /*@VisibleForTesting*/
-internal val StartIconIndicatorVerticalPadding = (StartIconItemActiveIndicatorHeight - IconSize) / 2
+internal val StartIconIndicatorVerticalPadding =
+    (NavigationBarHorizontalItemTokens.ActiveIndicatorHeight -
+        NavigationBarHorizontalItemTokens.IconSize) / 2
 /*@VisibleForTesting*/
 internal val TopIconIndicatorToLabelPadding: Dp = 4.dp
 /*@VisibleForTesting*/
-internal val StartIconIndicatorHorizontalPadding = 16.dp /* TODO: Replace with token. */
+internal val StartIconIndicatorHorizontalPadding =
+    NavigationBarHorizontalItemTokens.ActiveIndicatorLeadingSpace
 /*@VisibleForTesting*/
-internal val StartIconToLabelPadding = 4.dp /* TODO: Replace with token. */
+internal val StartIconToLabelPadding = NavigationBarTokens.ItemActiveIndicatorIconLabelSpace
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
index 16a7573..024072c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
@@ -16,10 +16,13 @@
 
 package androidx.compose.material3
 
-import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
@@ -27,24 +30,28 @@
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerBasedShape
 import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.GenericShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.internal.ProvideContentColorTextStyle
+import androidx.compose.material3.tokens.MotionSchemeKeyTokens
+import androidx.compose.material3.tokens.ShapeTokens
 import androidx.compose.material3.tokens.SplitButtonSmallTokens
 import androidx.compose.material3.tokens.StateTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawOutline
 import androidx.compose.ui.layout.Layout
@@ -63,6 +70,9 @@
 import androidx.compose.ui.util.fastFirst
 import androidx.compose.ui.util.fastMaxOfOrNull
 import androidx.compose.ui.util.fastSumBy
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
 /**
  * A [SplitButton] let user define a button group consisting of 2 buttons. The leading button
@@ -158,17 +168,18 @@
             SplitButtonDefaults.LeadingButton(
                 onClick = onLeadingButtonClick,
                 enabled = enabled,
-                shape = SplitButtonDefaults.leadingButtonShape(innerCornerSize),
+                shapes = SplitButtonDefaults.leadingButtonShapes(innerCornerSize),
                 content = leadingContent
             )
         },
         trailingButton = {
-            SplitButtonDefaults.AnimatedTrailingButton(
+            SplitButtonDefaults.TrailingButton(
                 onClick = onTrailingButtonClick,
                 modifier = Modifier,
                 enabled = enabled,
                 checked = checked,
-                startCornerSize = innerCornerSize,
+                shapes =
+                    SplitButtonDefaults.trailingButtonShapes(startCornerSize = innerCornerSize),
                 content = trailingContent,
             )
         },
@@ -227,8 +238,9 @@
         leadingButton = {
             TonalLeadingButton(
                 onClick = onLeadingButtonClick,
+                modifier = Modifier,
                 enabled = enabled,
-                endCornerSize = innerCornerSize,
+                shapes = SplitButtonDefaults.leadingButtonShapes(endCornerSize = innerCornerSize),
                 content = leadingContent,
             )
         },
@@ -237,8 +249,8 @@
                 onClick = onTrailingButtonClick,
                 modifier = Modifier,
                 enabled = enabled,
-                startCornerSize = innerCornerSize,
                 checked = checked,
+                shapes = SplitButtonDefaults.trailingButtonShapes(innerCornerSize),
                 content = trailingContent,
             )
         },
@@ -300,8 +312,9 @@
         leadingButton = {
             ElevatedLeadingButton(
                 onClick = onLeadingButtonClick,
+                modifier = Modifier,
                 enabled = enabled,
-                endCornerSize = innerCornerSize,
+                shapes = SplitButtonDefaults.leadingButtonShapes(endCornerSize = innerCornerSize),
                 content = leadingContent,
             )
         },
@@ -310,8 +323,9 @@
                 onClick = onTrailingButtonClick,
                 modifier = Modifier,
                 enabled = enabled,
-                startCornerSize = innerCornerSize,
                 checked = checked,
+                shapes =
+                    SplitButtonDefaults.trailingButtonShapes(startCornerSize = innerCornerSize),
                 content = trailingContent
             )
         },
@@ -373,8 +387,9 @@
         leadingButton = {
             OutlinedLeadingButton(
                 onClick = onLeadingButtonClick,
+                modifier = Modifier,
                 enabled = enabled,
-                endCornerSize = innerCornerSize,
+                shapes = SplitButtonDefaults.leadingButtonShapes(innerCornerSize),
                 content = leadingContent,
             )
         },
@@ -383,7 +398,7 @@
                 onClick = onTrailingButtonClick,
                 modifier = Modifier,
                 enabled = enabled,
-                startCornerSize = innerCornerSize,
+                shapes = SplitButtonDefaults.trailingButtonShapes(innerCornerSize),
                 checked = checked,
                 content = trailingContent
             )
@@ -475,6 +490,8 @@
     /** Default size for the leading button end corners and trailing button start corners */
     val InnerCornerSize = CornerSize(4.dp)
 
+    private val InnerCornerSizePressed = CornerSize(8.dp)
+
     /**
      * Default percentage size for the leading button start corners and trailing button end corners
      */
@@ -503,21 +520,54 @@
     /** Trailng button state layer alpha when in checked state */
     private const val TrailingButtonStateLayerAlpha = StateTokens.PressedStateLayerOpacity
 
+    /** Default shape of the leading button. */
+    private fun leadingButtonShape(endCornerSize: CornerSize = InnerCornerSize) =
+        RoundedCornerShape(OuterCornerSize, endCornerSize, endCornerSize, OuterCornerSize)
+
+    private val LeadingPressedShape =
+        RoundedCornerShape(
+            topStart = OuterCornerSize,
+            bottomStart = OuterCornerSize,
+            topEnd = InnerCornerSizePressed,
+            bottomEnd = InnerCornerSizePressed
+        )
+    private val TrailingPressedShape =
+        RoundedCornerShape(
+            topStart = InnerCornerSizePressed,
+            bottomStart = InnerCornerSizePressed,
+            topEnd = OuterCornerSize,
+            bottomEnd = OuterCornerSize
+        )
+    private val TrailingCheckedShape = ShapeTokens.CornerFull
+
     /**
-     * Default shape of the leading button.
+     * Default shapes for the leading button. This defines the shapes the leading button should
+     * morph to when enabled, pressed etc.
      *
      * @param endCornerSize the size for top end corner and bottom end corner
      */
-    fun leadingButtonShape(endCornerSize: CornerSize = InnerCornerSize) =
-        RoundedCornerShape(OuterCornerSize, endCornerSize, endCornerSize, OuterCornerSize)
+    fun leadingButtonShapes(endCornerSize: CornerSize = InnerCornerSize) =
+        SplitButtonShapes(
+            shape = leadingButtonShape(endCornerSize),
+            pressedShape = LeadingPressedShape,
+            checkedShape = null,
+        )
+
+    /** Default shape of the trailing button */
+    private fun trailingButtonShape(startCornerSize: CornerSize = InnerCornerSize) =
+        RoundedCornerShape(startCornerSize, OuterCornerSize, OuterCornerSize, startCornerSize)
 
     /**
-     * Default shape of the trailing button
+     * Default shapes for the trailing button
      *
      * @param startCornerSize the size for top start corner and bottom start corner
      */
-    fun trailingButtonShape(startCornerSize: CornerSize = InnerCornerSize) =
-        RoundedCornerShape(startCornerSize, OuterCornerSize, OuterCornerSize, startCornerSize)
+    fun trailingButtonShapes(startCornerSize: CornerSize = InnerCornerSize) =
+        SplitButtonShapes(
+            shape = trailingButtonShape(startCornerSize),
+            pressedShape = TrailingPressedShape,
+            checkedShape = TrailingCheckedShape
+        )
 
     /**
      * Create a default `leading` button that has the same visual as a Filled[Button]. To create a
@@ -530,7 +580,7 @@
      * @param modifier the [Modifier] to be applied to this button.
      * @param enabled controls the enabled state of the split button. When `false`, this component
      *   will
-     * @param shape defines the shape of this button's container, border (when [border] is not
+     * @param shapes defines the shapes of this button's container, border (when [border] is not
      *   null), and shadow (when using [elevation])
      * @param colors [ButtonColors] that will be used to resolve the colors for this button in
      *   different states. See [ButtonDefaults.buttonColors].
@@ -553,7 +603,7 @@
         onClick: () -> Unit,
         modifier: Modifier = Modifier,
         enabled: Boolean = true,
-        shape: Shape = leadingButtonShape(),
+        shapes: SplitButtonShapes = leadingButtonShapes(),
         colors: ButtonColors = ButtonDefaults.buttonColors(),
         elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
         border: BorderStroke? = null,
@@ -563,11 +613,16 @@
     ) {
         @Suppress("NAME_SHADOWING")
         val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+
+        // TODO Load the motionScheme tokens from the component tokens file
+        val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
+        val pressed by interactionSource.collectIsPressedAsState()
+
         Surface(
             onClick = onClick,
             modifier = modifier.semantics { role = Role.Button },
             enabled = enabled,
-            shape = shape,
+            shape = shapeByInteraction(shapes, pressed, checked = false, defaultAnimationSpec),
             color = colors.containerColor,
             contentColor = colors.contentColor,
             shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
@@ -590,80 +645,9 @@
     }
 
     /**
-     * Create a default `trailing` button that has the same visual as a Filled[Button]. For a
-     * `trailing` button that offers corner morphing animation, see [AnimatedTrailingButton].
-     *
-     * To create a `tonal`, `outlined`, or `elevated` version, the default value of [Button] params
-     * can be passed in. For example, [ElevatedButton].
-     *
-     * The default text style for internal [Text] components will be set to [Typography.labelLarge].
-     *
-     * @param onClick called when the button is clicked
-     * @param modifier the [Modifier] to be applied to this button.
-     * @param shape defines the shape of this button's container, border (when [border] is not
-     *   null), and shadow (when using [elevation]). [TrailingButton]
-     * @param enabled controls the enabled state of the split button. When `false`, this component
-     *   will
-     * @param colors [ButtonColors] that will be used to resolve the colors for this button in
-     *   different states. See [ButtonDefaults.buttonColors].
-     * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
-     *   states. This controls the size of the shadow below the button. See
-     *   [ButtonElevation.shadowElevation].
-     * @param border the border to draw around the container of this button contentPadding the
-     *   spacing values to apply internally between the container and the content
-     * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
-     *   emitting [Interaction]s for this button. You can use this to change the button's appearance
-     *   or preview the button in different states. Note that if `null` is provided, interactions
-     *   will still happen internally.
-     * @param content the content to be placed inside a button
-     */
-    @ExperimentalMaterial3ExpressiveApi
-    @Composable
-    fun TrailingButton(
-        onClick: () -> Unit,
-        modifier: Modifier = Modifier,
-        shape: Shape = trailingButtonShape(),
-        enabled: Boolean = true,
-        colors: ButtonColors = ButtonDefaults.buttonColors(),
-        elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
-        border: BorderStroke? = null,
-        interactionSource: MutableInteractionSource? = null,
-        content: @Composable RowScope.() -> Unit
-    ) {
-        @Suppress("NAME_SHADOWING")
-        val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-
-        Surface(
-            onClick = onClick,
-            modifier = modifier.semantics { role = Role.Button },
-            enabled = enabled,
-            shape = shape,
-            color = colors.containerColor,
-            contentColor = colors.contentColor,
-            shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
-            border = border,
-            interactionSource = interactionSource
-        ) {
-            ProvideContentColorTextStyle(
-                contentColor = colors.contentColor,
-                textStyle = MaterialTheme.typography.labelLarge
-            ) {
-                Row(
-                    Modifier.defaultMinSize(
-                        minWidth = TrailingButtonMinWidth,
-                        minHeight = MinHeight
-                    ),
-                    horizontalArrangement = Arrangement.Center,
-                    verticalAlignment = Alignment.CenterVertically,
-                    content = content
-                )
-            }
-        }
-    }
-
-    /**
-     * Create a animated `trailing` button that has the same visual as a Filled[Button]. When
-     * [checked] is updated from `false` to `true`, the buttons corners will morph to `full`.
+     * Creates a `trailing` button that has the same visual as a Filled[Button]. When [checked] is
+     * updated from `false` to `true`, the buttons corners will morph to `full` by default. Pressed
+     * shape and checked shape can be customized via [shapes] param.
      *
      * To create a `tonal`, `outlined`, or `elevated` version, the default value of [Button] params
      * can be passed in. For example, [ElevatedButton].
@@ -676,7 +660,8 @@
      * @param modifier the [Modifier] to be applied to this button.
      * @param enabled controls the enabled state of the split button. When `false`, this component
      *   will
-     * @param startCornerSize The size for top start corner and bottom start corner
+     * @param shapes the shapes for the container when transition between different
+     *   [ShapeAnimationState]
      * @param colors [ButtonColors] that will be used to resolve the colors for this button in
      *   different states. See [ButtonDefaults.buttonColors].
      * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
@@ -694,12 +679,12 @@
      */
     @Composable
     @ExperimentalMaterial3ExpressiveApi
-    fun AnimatedTrailingButton(
+    fun TrailingButton(
         onClick: () -> Unit,
         checked: Boolean,
         modifier: Modifier = Modifier,
         enabled: Boolean = true,
-        startCornerSize: CornerSize = InnerCornerSize,
+        shapes: SplitButtonShapes = trailingButtonShapes(),
         colors: ButtonColors = ButtonDefaults.buttonColors(),
         elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
         border: BorderStroke? = null,
@@ -707,102 +692,90 @@
         interactionSource: MutableInteractionSource? = null,
         content: @Composable RowScope.() -> Unit
     ) {
-        val cornerMorphProgress: Float by animateFloatAsState(if (checked) 1f else 0f)
         @Suppress("NAME_SHADOWING")
         val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-        val density = LocalDensity.current
-        val shape = rememberTrailingButtonShape(density, startCornerSize) { cornerMorphProgress }
 
-        TrailingButton(
+        // TODO Load the motionScheme tokens from the component tokens file
+        val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
+        val pressed by interactionSource.collectIsPressedAsState()
+
+        val density = LocalDensity.current
+        val shape = shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
+
+        Surface(
             onClick = onClick,
             modifier =
-                modifier.drawWithContent {
-                    drawContent()
-                    if (checked) {
-                        drawOutline(
-                            outline = shape.createOutline(size, layoutDirection, density),
-                            color = colors.contentColor,
-                            alpha = TrailingButtonStateLayerAlpha
-                        )
+                modifier
+                    .drawWithContent {
+                        drawContent()
+                        if (checked) {
+                            drawOutline(
+                                outline = shape.createOutline(size, layoutDirection, density),
+                                color = colors.contentColor,
+                                alpha = TrailingButtonStateLayerAlpha
+                            )
+                        }
                     }
-                },
+                    .semantics { role = Role.Button },
             enabled = enabled,
-            colors = colors,
-            elevation = elevation,
-            border = border,
-            interactionSource = interactionSource,
             shape = shape,
+            color = colors.containerColor,
+            contentColor = colors.contentColor,
+            shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
+            border = border,
+            interactionSource = interactionSource
         ) {
-            Row(
-                modifier =
-                    modifier.opticalCentering(
-                        trailingButtonShape(if (checked) OuterCornerSize else startCornerSize),
-                        contentPadding
-                    ),
-                content = content
-            )
+            ProvideContentColorTextStyle(
+                contentColor = colors.contentColor,
+                textStyle = MaterialTheme.typography.labelLarge
+            ) {
+                Row(
+                    Modifier.defaultMinSize(
+                            minWidth = TrailingButtonMinWidth,
+                            minHeight = MinHeight
+                        )
+                        .then(
+                            when (shape) {
+                                is ShapeWithOpticalCentering -> {
+                                    Modifier.opticalCentering(
+                                        shape = shape,
+                                        basePadding = contentPadding
+                                    )
+                                }
+                                is CornerBasedShape -> {
+                                    Modifier.opticalCentering(
+                                        shape = shape,
+                                        basePadding = contentPadding
+                                    )
+                                }
+                                else -> {
+                                    Modifier.padding(contentPadding)
+                                }
+                            }
+                        ),
+                    horizontalArrangement = Arrangement.Center,
+                    verticalAlignment = Alignment.CenterVertically,
+                    content = content
+                )
+            }
         }
     }
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
-private fun rememberTrailingButtonShape(
-    density: Density,
-    startCornerSize: CornerSize,
-    progress: () -> Float
-) =
-    remember(density, progress) {
-        GenericShape { size, layoutDirection ->
-            val rect = Rect(Offset.Zero, size)
-            val startCornerSizePx = startCornerSize.toPx(size, density)
-            val originalStartCornerRadius = CornerRadius(startCornerSizePx)
-            val endCornerRadius =
-                CornerRadius(SplitButtonDefaults.OuterCornerSize.toPx(size, density))
-            val originalRoundRect =
-                if (layoutDirection == LayoutDirection.Rtl) {
-                    RoundRect(
-                        rect,
-                        endCornerRadius,
-                        originalStartCornerRadius,
-                        originalStartCornerRadius,
-                        endCornerRadius
-                    )
-                } else {
-                    RoundRect(
-                        rect,
-                        originalStartCornerRadius,
-                        endCornerRadius,
-                        endCornerRadius,
-                        originalStartCornerRadius
-                    )
-                }
-
-            val endRoundRect =
-                RoundRect(
-                    rect,
-                    CornerRadius(SplitButtonDefaults.OuterCornerSize.toPx(size, density))
-                )
-
-            val roundRect = lerp(originalRoundRect, endRoundRect, progress.invoke())
-            addRoundRect(roundRect)
-        }
-    }
-
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
-@Composable
 private fun TonalLeadingButton(
     onClick: () -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    endCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
     SplitButtonDefaults.LeadingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        shape = SplitButtonDefaults.leadingButtonShape(endCornerSize),
+        shapes = shapes,
         colors = ButtonDefaults.filledTonalButtonColors(),
         elevation = ButtonDefaults.filledTonalButtonElevation(),
         border = null,
@@ -815,16 +788,16 @@
 private fun TonalTrailingButton(
     onClick: () -> Unit,
     checked: Boolean,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    startCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
-    SplitButtonDefaults.AnimatedTrailingButton(
+    SplitButtonDefaults.TrailingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        startCornerSize = startCornerSize,
+        shapes = shapes,
         checked = checked,
         colors = ButtonDefaults.filledTonalButtonColors(),
         elevation = ButtonDefaults.filledTonalButtonElevation(),
@@ -837,16 +810,16 @@
 @Composable
 private fun OutlinedLeadingButton(
     onClick: () -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    endCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
     SplitButtonDefaults.LeadingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        shape = SplitButtonDefaults.leadingButtonShape(endCornerSize),
+        shapes = shapes,
         colors = ButtonDefaults.outlinedButtonColors(),
         elevation = null,
         border = ButtonDefaults.outlinedButtonBorder(enabled),
@@ -859,16 +832,16 @@
 private fun OutlinedTrailingButton(
     onClick: () -> Unit,
     checked: Boolean,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    startCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
-    SplitButtonDefaults.AnimatedTrailingButton(
+    SplitButtonDefaults.TrailingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        startCornerSize = startCornerSize,
+        shapes = shapes,
         checked = checked,
         colors = ButtonDefaults.outlinedButtonColors(),
         elevation = null,
@@ -881,16 +854,16 @@
 @Composable
 private fun ElevatedLeadingButton(
     onClick: () -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    endCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
     SplitButtonDefaults.LeadingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        shape = SplitButtonDefaults.leadingButtonShape(endCornerSize),
+        shapes = shapes,
         colors = ButtonDefaults.elevatedButtonColors(),
         elevation = ButtonDefaults.elevatedButtonElevation(),
         border = null,
@@ -903,16 +876,16 @@
 private fun ElevatedTrailingButton(
     onClick: () -> Unit,
     checked: Boolean,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    startCornerSize: CornerSize,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: SplitButtonShapes,
     content: @Composable RowScope.() -> Unit
 ) {
-    SplitButtonDefaults.AnimatedTrailingButton(
+    SplitButtonDefaults.TrailingButton(
         modifier = modifier,
         onClick = onClick,
         enabled = enabled,
-        startCornerSize = startCornerSize,
+        shapes = shapes,
         checked = checked,
         colors = ButtonDefaults.elevatedButtonColors(),
         elevation = ButtonDefaults.elevatedButtonElevation(),
@@ -921,5 +894,158 @@
     )
 }
 
+@Composable
+private fun shapeByInteraction(
+    shapes: SplitButtonShapes,
+    pressed: Boolean,
+    checked: Boolean,
+    animationSpec: FiniteAnimationSpec<Float>
+): Shape {
+    if (shapes.hasRoundedCornerShapes) {
+        return rememberAnimatedShape(shapes, pressed, checked, animationSpec)
+    }
+    if (pressed) {
+        return shapes.pressedShape ?: shapes.shape
+    }
+    if (checked) {
+        return shapes.checkedShape ?: shapes.shape
+    }
+    return shapes.shape
+}
+
+@Composable
+private fun rememberAnimatedShape(
+    shapes: SplitButtonShapes,
+    pressed: Boolean,
+    checked: Boolean,
+    animationSpec: FiniteAnimationSpec<Float>,
+): Shape {
+    val defaultShape = shapes.shape as RoundedCornerShape
+    val pressedShape = shapes.pressedShape as RoundedCornerShape?
+    val checkedShape = shapes.checkedShape as RoundedCornerShape?
+    val currentShape =
+        if (pressedShape != null && pressed) pressedShape
+        else if (checkedShape != null && checked) checkedShape else defaultShape
+
+    val state =
+        remember(animationSpec) {
+            ShapeAnimationState(
+                shape = currentShape,
+                spec = animationSpec,
+            )
+        }
+
+    val channel = remember { Channel<RoundedCornerShape>(Channel.CONFLATED) }
+    SideEffect { channel.trySend(currentShape) }
+    LaunchedEffect(state, channel) {
+        for (target in channel) {
+            val newTarget = channel.tryReceive().getOrNull() ?: target
+            launch { state.animateToShape(newTarget) }
+        }
+    }
+
+    return rememberAnimatedShape(state)
+}
+
+@Composable
+private fun rememberAnimatedShape(state: ShapeAnimationState): Shape {
+    val density = LocalDensity.current
+    state.density = density
+
+    return remember(density, state) {
+        object : ShapeWithOpticalCentering {
+            var clampedRange by mutableStateOf(0f..1f)
+
+            override fun offset(): Float {
+                val topStart = state.topStart?.value?.coerceIn(clampedRange) ?: 0f
+                val topEnd = state.topEnd?.value?.coerceIn(clampedRange) ?: 0f
+                val bottomStart = state.bottomStart?.value?.coerceIn(clampedRange) ?: 0f
+                val bottomEnd = state.bottomEnd?.value?.coerceIn(clampedRange) ?: 0f
+                val avgStart = (topStart + bottomStart) / 2
+                val avgEnd = (topEnd + bottomEnd) / 2
+                return OpticalCenteringCoefficient * (avgStart - avgEnd)
+            }
+
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ): Outline {
+                state.size = size
+                if (!state.didInit) {
+                    state.init()
+                }
+
+                clampedRange = 0f..size.height / 2
+                return RoundedCornerShape(
+                        topStart = state.topStart?.value?.coerceIn(clampedRange) ?: 0f,
+                        topEnd = state.topEnd?.value?.coerceIn(clampedRange) ?: 0f,
+                        bottomStart = state.bottomStart?.value?.coerceIn(clampedRange) ?: 0f,
+                        bottomEnd = state.bottomEnd?.value?.coerceIn(clampedRange) ?: 0f,
+                    )
+                    .createOutline(size, layoutDirection, density)
+            }
+        }
+    }
+}
+
+/**
+ * The shapes that will be used in [SplitButton]. Split button will morph between these shapes
+ * depending on the interaction of the buttons, assuming all of the shapes are [CornerBasedShape]s.
+ *
+ * @property shape is the default shape.
+ * @property pressedShape is the pressed shape.
+ * @property checkedShape is the checked shape.
+ */
+data class SplitButtonShapes(val shape: Shape, val pressedShape: Shape?, val checkedShape: Shape?)
+
+internal val SplitButtonShapes.hasRoundedCornerShapes: Boolean
+    get() {
+        // Ignore null shapes and only check default shape for RoundedCorner
+        if (pressedShape != null && pressedShape !is RoundedCornerShape) return false
+        if (checkedShape != null && checkedShape !is RoundedCornerShape) return false
+        return shape is RoundedCornerShape
+    }
+
+@Stable
+private class ShapeAnimationState(
+    val shape: RoundedCornerShape,
+    val spec: FiniteAnimationSpec<Float>,
+) {
+    var size: Size = Size.Zero
+    var density: Density = Density(0f, 0f)
+    var didInit = false
+
+    var topStart: Animatable<Float, AnimationVector1D>? = null
+        private set
+
+    var topEnd: Animatable<Float, AnimationVector1D>? = null
+        private set
+
+    var bottomStart: Animatable<Float, AnimationVector1D>? = null
+        private set
+
+    var bottomEnd: Animatable<Float, AnimationVector1D>? = null
+        private set
+
+    fun init() {
+        topStart = Animatable(shape.topStart.toPx(size, density))
+        topEnd = Animatable(shape.topEnd.toPx(size, density))
+        bottomStart = Animatable(shape.bottomStart.toPx(size, density))
+        bottomEnd = Animatable(shape.bottomEnd.toPx(size, density))
+        didInit = true
+    }
+
+    suspend fun animateToShape(shape: RoundedCornerShape?) =
+        shape?.let {
+            coroutineScope {
+                launch { topStart?.animateTo(it.topStart.toPx(size, density), spec) }
+                launch { topEnd?.animateTo(it.topEnd.toPx(size, density), spec) }
+                launch { bottomStart?.animateTo(it.bottomStart.toPx(size, density), spec) }
+                launch { bottomEnd?.animateTo(it.bottomEnd.toPx(size, density), spec) }
+            }
+        }
+}
+
 private const val LeadingButtonLayoutId = "LeadingButton"
 private const val TrailingButtonLayoutId = "TrailingButton"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
index 362c048..11f8448 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
@@ -41,6 +41,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -60,6 +61,8 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import kotlin.jvm.JvmInline
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
@@ -128,37 +131,16 @@
 ) {
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-    val isCornerBasedShape =
-        shapes.shape is CornerBasedShape &&
-            shapes.checkedShape is CornerBasedShape &&
-            shapes.pressedShape is CornerBasedShape
     // TODO Load the motionScheme tokens from the component tokens file
     // MotionSchemeKeyTokens.DefaultEffects is intentional here to prevent
     // any bounce in this component.
     val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
     val pressed by interactionSource.collectIsPressedAsState()
-
-    val state: AnimatedShapeState? =
-        if (isCornerBasedShape) {
-            val defaultShape = shapes.shape as CornerBasedShape
-            val pressedShape = shapes.pressedShape as CornerBasedShape
-            val checkedShape = shapes.checkedShape as CornerBasedShape
-            remember {
-                AnimatedShapeState(
-                    startShape = if (checked) checkedShape else defaultShape,
-                    defaultShape = defaultShape,
-                    pressedShape = pressedShape,
-                    checkedShape = checkedShape,
-                    spec = defaultAnimationSpec,
-                )
-            }
-        } else null
-
     val containerColor = colors.containerColor(enabled, checked)
     val contentColor = colors.contentColor(enabled, checked)
     val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
 
-    val buttonShape = shapeByInteraction(isCornerBasedShape, state, shapes, pressed, checked)
+    val buttonShape = shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
 
     Surface(
         checked = checked,
@@ -876,36 +858,79 @@
  */
 data class ButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape)
 
+internal val ButtonShapes.hasRoundedCornerShapes: Boolean
+    get() =
+        shape is RoundedCornerShape &&
+            pressedShape is RoundedCornerShape &&
+            checkedShape is RoundedCornerShape
+
 @Composable
 private fun shapeByInteraction(
-    isCornerBasedShape: Boolean,
-    state: AnimatedShapeState?,
     shapes: ButtonShapes,
     pressed: Boolean,
-    checked: Boolean
+    checked: Boolean,
+    animationSpec: FiniteAnimationSpec<Float>
 ): Shape {
-    return if (isCornerBasedShape) {
-        if (state != null) {
-            LaunchedEffect(pressed, checked) {
-                if (pressed) {
-                    state.animateToPressed()
-                } else if (checked) {
-                    state.animateToChecked()
-                } else {
-                    state.animateToDefault()
+    if (shapes.hasRoundedCornerShapes) {
+        return rememberAnimatedShape(shapes, checked, animationSpec, pressed)
+    }
+
+    if (pressed) {
+        return shapes.pressedShape
+    }
+
+    if (checked) {
+        return shapes.checkedShape
+    }
+
+    return shapes.shape
+}
+
+@Composable
+private fun rememberAnimatedShape(
+    shapes: ButtonShapes,
+    checked: Boolean,
+    animationSpec: FiniteAnimationSpec<Float>,
+    pressed: Boolean
+): Shape {
+    val defaultShape = shapes.shape as CornerBasedShape
+    val pressedShape = shapes.pressedShape as CornerBasedShape
+    val checkedShape = shapes.checkedShape as CornerBasedShape
+    val state =
+        remember(shapes, animationSpec) {
+            AnimatedShapeState(
+                startShape = if (checked) checkedShape else defaultShape,
+                defaultShape = defaultShape,
+                pressedShape = pressedShape,
+                checkedShape = checkedShape,
+                spec = animationSpec,
+            )
+        }
+
+    val channel = remember { Channel<AnimatedShapeValue>(Channel.CONFLATED) }
+    val targetValue =
+        when {
+            pressed -> AnimatedShapeValue.Pressed
+            checked -> AnimatedShapeValue.Checked
+            else -> AnimatedShapeValue.Default
+        }
+    SideEffect { channel.trySend(targetValue) }
+    LaunchedEffect(state, channel) {
+        for (target in channel) {
+            val newTarget = channel.tryReceive().getOrNull() ?: target
+            launch {
+                with(state) {
+                    when (newTarget) {
+                        AnimatedShapeValue.Pressed -> animateToPressed()
+                        AnimatedShapeValue.Checked -> animateToChecked()
+                        else -> animateToDefault()
+                    }
                 }
             }
-            rememberAnimatedShape(state)
-        } else {
-            shapes.shape
         }
-    } else if (pressed) {
-        shapes.pressedShape
-    } else if (checked) {
-        shapes.checkedShape
-    } else {
-        shapes.shape
     }
+
+    return rememberAnimatedShape(state)
 }
 
 @Composable
@@ -913,7 +938,7 @@
     val density = LocalDensity.current
     state.density = density
 
-    return remember(density) {
+    return remember(density, state) {
         object : ShapeWithOpticalCentering {
             var clampedRange by mutableStateOf(0f..1f)
 
@@ -995,3 +1020,14 @@
         launch { bottomEnd?.animateTo(shape.bottomEnd.toPx(size, density), spec) }
     }
 }
+
+@Immutable
+@JvmInline
+internal value class AnimatedShapeValue internal constructor(internal val type: Int) {
+
+    companion object {
+        val Default = AnimatedShapeValue(0)
+        val Pressed = AnimatedShapeValue(1)
+        val Checked = AnimatedShapeValue(2)
+    }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Typography.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Typography.kt
index 0bfc8d5..a9f9262 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Typography.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Typography.kt
@@ -34,6 +34,11 @@
  * The type scale is a combination of thirteen styles that are supported by the type system. It
  * contains reusable categories of text, each with an intended application and meaning.
  *
+ * The emphasized versions of the baseline styles add dynamism and personality to the baseline
+ * styles. It can be used to further stylize select pieces of text. The emphasized states have
+ * pragmatic uses, such as creating clearer division of content and drawing users' eyes to relevant
+ * material.
+ *
  * To learn more about typography, see
  * [Material Design typography](https://m3.material.io/styles/typography/overview).
  *
@@ -77,9 +82,26 @@
  *   annotate imagery or to introduce a headline.
  * @property labelSmall labelSmall is one of the smallest font sizes. It is used sparingly to
  *   annotate imagery or to introduce a headline.
+ * @property displayLargeEmphasized an emphasized version of [displayLarge].
+ * @property displayMediumEmphasized an emphasized version of [displayMedium].
+ * @property displaySmallEmphasized an emphasized version of [displaySmall].
+ * @property headlineLargeEmphasized an emphasized version of [headlineLarge].
+ * @property headlineMediumEmphasized an emphasized version of [headlineMedium].
+ * @property headlineSmallEmphasized an emphasized version of [headlineSmall].
+ * @property titleLargeEmphasized an emphasized version of [titleLarge].
+ * @property titleMediumEmphasized an emphasized version of [titleMedium].
+ * @property titleSmallEmphasized an emphasized version of [titleSmall].
+ * @property bodyLargeEmphasized an emphasized version of [bodyLarge].
+ * @property bodyMediumEmphasized an emphasized version of [bodyMedium].
+ * @property bodySmallEmphasized an emphasized version of [bodySmall].
+ * @property labelLargeEmphasized an emphasized version of [labelLarge].
+ * @property labelMediumEmphasized an emphasized version of [labelMedium].
+ * @property labelSmallEmphasized an emphasized version of [labelSmall].
  */
 @Immutable
-class Typography(
+class Typography
+@ExperimentalMaterial3ExpressiveApi
+constructor(
     val displayLarge: TextStyle = TypographyTokens.DisplayLarge,
     val displayMedium: TextStyle = TypographyTokens.DisplayMedium,
     val displaySmall: TextStyle = TypographyTokens.DisplaySmall,
@@ -95,9 +117,285 @@
     val labelLarge: TextStyle = TypographyTokens.LabelLarge,
     val labelMedium: TextStyle = TypographyTokens.LabelMedium,
     val labelSmall: TextStyle = TypographyTokens.LabelSmall,
+    displayLargeEmphasized: TextStyle = TypographyTokens.DisplayLargeEmphasized,
+    displayMediumEmphasized: TextStyle = TypographyTokens.DisplayMediumEmphasized,
+    displaySmallEmphasized: TextStyle = TypographyTokens.DisplaySmallEmphasized,
+    headlineLargeEmphasized: TextStyle = TypographyTokens.HeadlineLargeEmphasized,
+    headlineMediumEmphasized: TextStyle = TypographyTokens.HeadlineMediumEmphasized,
+    headlineSmallEmphasized: TextStyle = TypographyTokens.HeadlineSmallEmphasized,
+    titleLargeEmphasized: TextStyle = TypographyTokens.TitleLargeEmphasized,
+    titleMediumEmphasized: TextStyle = TypographyTokens.TitleMediumEmphasized,
+    titleSmallEmphasized: TextStyle = TypographyTokens.TitleSmallEmphasized,
+    bodyLargeEmphasized: TextStyle = TypographyTokens.BodyLargeEmphasized,
+    bodyMediumEmphasized: TextStyle = TypographyTokens.BodyMediumEmphasized,
+    bodySmallEmphasized: TextStyle = TypographyTokens.BodySmallEmphasized,
+    labelLargeEmphasized: TextStyle = TypographyTokens.LabelLargeEmphasized,
+    labelMediumEmphasized: TextStyle = TypographyTokens.LabelMediumEmphasized,
+    labelSmallEmphasized: TextStyle = TypographyTokens.LabelSmallEmphasized,
 ) {
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [displayLarge]. */
+    val displayLargeEmphasized = displayLargeEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [displayMedium]. */
+    val displayMediumEmphasized = displayMediumEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [displaySmall]. */
+    val displaySmallEmphasized = displaySmallEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [headlineLarge]. */
+    val headlineLargeEmphasized = headlineLargeEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [headlineMedium]. */
+    val headlineMediumEmphasized = headlineMediumEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [headlineSmall]. */
+    val headlineSmallEmphasized = headlineSmallEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [titleLarge]. */
+    val titleLargeEmphasized = titleLargeEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [titleMedium]. */
+    val titleMediumEmphasized = titleMediumEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [titleSmall]. */
+    val titleSmallEmphasized = titleSmallEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [bodyLarge]. */
+    val bodyLargeEmphasized = bodyLargeEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [bodyMedium]. */
+    val bodyMediumEmphasized = bodyMediumEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [bodySmall]. */
+    val bodySmallEmphasized = bodySmallEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [labelLarge]. */
+    val labelLargeEmphasized = labelLargeEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [labelMedium]. */
+    val labelMediumEmphasized = labelMediumEmphasized
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** an emphasized version of [labelSmall]. */
+    val labelSmallEmphasized = labelSmallEmphasized
+
+    /**
+     * The Material Design type scale includes a range of contrasting styles that support the needs
+     * of your product and its content.
+     *
+     * Use typography to make writing legible and beautiful. Material's default type scale includes
+     * contrasting and flexible styles to support a wide range of use cases.
+     *
+     * The type scale is a combination of thirteen styles that are supported by the type system. It
+     * contains reusable categories of text, each with an intended application and meaning.
+     *
+     * To learn more about typography, see
+     * [Material Design typography](https://m3.material.io/styles/typography/overview).
+     *
+     * @param displayLarge displayLarge is the largest display text.
+     * @param displayMedium displayMedium is the second largest display text.
+     * @param displaySmall displaySmall is the smallest display text.
+     * @param headlineLarge headlineLarge is the largest headline, reserved for short, important
+     *   text or numerals. For headlines, you can choose an expressive font, such as a display,
+     *   handwritten, or script style. These unconventional font designs have details and intricacy
+     *   that help attract the eye.
+     * @param headlineMedium headlineMedium is the second largest headline, reserved for short,
+     *   important text or numerals. For headlines, you can choose an expressive font, such as a
+     *   display, handwritten, or script style. These unconventional font designs have details and
+     *   intricacy that help attract the eye.
+     * @param headlineSmall headlineSmall is the smallest headline, reserved for short, important
+     *   text or numerals. For headlines, you can choose an expressive font, such as a display,
+     *   handwritten, or script style. These unconventional font designs have details and intricacy
+     *   that help attract the eye.
+     * @param titleLarge titleLarge is the largest title, and is typically reserved for
+     *   medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+     *   subtitles.
+     * @param titleMedium titleMedium is the second largest title, and is typically reserved for
+     *   medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+     *   subtitles.
+     * @param titleSmall titleSmall is the smallest title, and is typically reserved for
+     *   medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+     *   subtitles.
+     * @param bodyLarge bodyLarge is the largest body, and is typically used for long-form writing
+     *   as it works well for small text sizes. For longer sections of text, a serif or sans serif
+     *   typeface is recommended.
+     * @param bodyMedium bodyMedium is the second largest body, and is typically used for long-form
+     *   writing as it works well for small text sizes. For longer sections of text, a serif or sans
+     *   serif typeface is recommended.
+     * @param bodySmall bodySmall is the smallest body, and is typically used for long-form writing
+     *   as it works well for small text sizes. For longer sections of text, a serif or sans serif
+     *   typeface is recommended.
+     * @param labelLarge labelLarge text is a call to action used in different types of buttons
+     *   (such as text, outlined and contained buttons) and in tabs, dialogs, and cards. Button text
+     *   is typically sans serif, using all caps text.
+     * @param labelMedium labelMedium is one of the smallest font sizes. It is used sparingly to
+     *   annotate imagery or to introduce a headline.
+     * @param labelSmall labelSmall is one of the smallest font sizes. It is used sparingly to
+     *   annotate imagery or to introduce a headline.
+     */
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    constructor(
+        displayLarge: TextStyle = TypographyTokens.DisplayLarge,
+        displayMedium: TextStyle = TypographyTokens.DisplayMedium,
+        displaySmall: TextStyle = TypographyTokens.DisplaySmall,
+        headlineLarge: TextStyle = TypographyTokens.HeadlineLarge,
+        headlineMedium: TextStyle = TypographyTokens.HeadlineMedium,
+        headlineSmall: TextStyle = TypographyTokens.HeadlineSmall,
+        titleLarge: TextStyle = TypographyTokens.TitleLarge,
+        titleMedium: TextStyle = TypographyTokens.TitleMedium,
+        titleSmall: TextStyle = TypographyTokens.TitleSmall,
+        bodyLarge: TextStyle = TypographyTokens.BodyLarge,
+        bodyMedium: TextStyle = TypographyTokens.BodyMedium,
+        bodySmall: TextStyle = TypographyTokens.BodySmall,
+        labelLarge: TextStyle = TypographyTokens.LabelLarge,
+        labelMedium: TextStyle = TypographyTokens.LabelMedium,
+        labelSmall: TextStyle = TypographyTokens.LabelSmall,
+    ) : this(
+        displayLarge = displayLarge,
+        displayMedium = displayMedium,
+        displaySmall = displaySmall,
+        headlineLarge = headlineLarge,
+        headlineMedium = headlineMedium,
+        headlineSmall = headlineSmall,
+        titleLarge = titleLarge,
+        titleMedium = titleMedium,
+        titleSmall = titleSmall,
+        bodyLarge = bodyLarge,
+        bodyMedium = bodyMedium,
+        bodySmall = bodySmall,
+        labelLarge = labelLarge,
+        labelMedium = labelMedium,
+        labelSmall = labelSmall,
+        displayLargeEmphasized = displayLarge,
+        displayMediumEmphasized = displayMedium,
+        displaySmallEmphasized = displaySmall,
+        headlineLargeEmphasized = headlineLarge,
+        headlineMediumEmphasized = headlineMedium,
+        headlineSmallEmphasized = headlineSmall,
+        titleLargeEmphasized = titleLarge,
+        titleMediumEmphasized = titleMedium,
+        titleSmallEmphasized = titleSmall,
+        bodyLargeEmphasized = bodyLarge,
+        bodyMediumEmphasized = bodyMedium,
+        bodySmallEmphasized = bodySmall,
+        labelLargeEmphasized = labelLarge,
+        labelMediumEmphasized = labelMedium,
+        labelSmallEmphasized = labelSmall,
+    )
 
     /** Returns a copy of this Typography, optionally overriding some of the values. */
+    @ExperimentalMaterial3ExpressiveApi
+    fun copy(
+        displayLarge: TextStyle = this.displayLarge,
+        displayMedium: TextStyle = this.displayMedium,
+        displaySmall: TextStyle = this.displaySmall,
+        headlineLarge: TextStyle = this.headlineLarge,
+        headlineMedium: TextStyle = this.headlineMedium,
+        headlineSmall: TextStyle = this.headlineSmall,
+        titleLarge: TextStyle = this.titleLarge,
+        titleMedium: TextStyle = this.titleMedium,
+        titleSmall: TextStyle = this.titleSmall,
+        bodyLarge: TextStyle = this.bodyLarge,
+        bodyMedium: TextStyle = this.bodyMedium,
+        bodySmall: TextStyle = this.bodySmall,
+        labelLarge: TextStyle = this.labelLarge,
+        labelMedium: TextStyle = this.labelMedium,
+        labelSmall: TextStyle = this.labelSmall,
+        displayLargeEmphasized: TextStyle = this.displayLargeEmphasized,
+        displayMediumEmphasized: TextStyle = this.displayMediumEmphasized,
+        displaySmallEmphasized: TextStyle = this.displaySmallEmphasized,
+        headlineLargeEmphasized: TextStyle = this.headlineLargeEmphasized,
+        headlineMediumEmphasized: TextStyle = this.headlineMediumEmphasized,
+        headlineSmallEmphasized: TextStyle = this.headlineSmallEmphasized,
+        titleLargeEmphasized: TextStyle = this.titleLargeEmphasized,
+        titleMediumEmphasized: TextStyle = this.titleMediumEmphasized,
+        titleSmallEmphasized: TextStyle = this.titleSmallEmphasized,
+        bodyLargeEmphasized: TextStyle = this.bodyLargeEmphasized,
+        bodyMediumEmphasized: TextStyle = this.bodyMediumEmphasized,
+        bodySmallEmphasized: TextStyle = this.bodySmallEmphasized,
+        labelLargeEmphasized: TextStyle = this.labelLargeEmphasized,
+        labelMediumEmphasized: TextStyle = this.labelMediumEmphasized,
+        labelSmallEmphasized: TextStyle = this.labelSmallEmphasized,
+    ): Typography =
+        Typography(
+            displayLarge = displayLarge,
+            displayMedium = displayMedium,
+            displaySmall = displaySmall,
+            headlineLarge = headlineLarge,
+            headlineMedium = headlineMedium,
+            headlineSmall = headlineSmall,
+            titleLarge = titleLarge,
+            titleMedium = titleMedium,
+            titleSmall = titleSmall,
+            bodyLarge = bodyLarge,
+            bodyMedium = bodyMedium,
+            bodySmall = bodySmall,
+            labelLarge = labelLarge,
+            labelMedium = labelMedium,
+            labelSmall = labelSmall,
+            displayLargeEmphasized = displayLargeEmphasized,
+            displayMediumEmphasized = displayMediumEmphasized,
+            displaySmallEmphasized = displaySmallEmphasized,
+            headlineLargeEmphasized = headlineLargeEmphasized,
+            headlineMediumEmphasized = headlineMediumEmphasized,
+            headlineSmallEmphasized = headlineSmallEmphasized,
+            titleLargeEmphasized = titleLargeEmphasized,
+            titleMediumEmphasized = titleMediumEmphasized,
+            titleSmallEmphasized = titleSmallEmphasized,
+            bodyLargeEmphasized = bodyLargeEmphasized,
+            bodyMediumEmphasized = bodyMediumEmphasized,
+            bodySmallEmphasized = bodySmallEmphasized,
+            labelLargeEmphasized = labelLargeEmphasized,
+            labelMediumEmphasized = labelMediumEmphasized,
+            labelSmallEmphasized = labelSmallEmphasized,
+        )
+
+    /** Returns a copy of this Typography, optionally overriding some of the values. */
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     fun copy(
         displayLarge: TextStyle = this.displayLarge,
         displayMedium: TextStyle = this.displayMedium,
@@ -115,7 +413,7 @@
         labelMedium: TextStyle = this.labelMedium,
         labelSmall: TextStyle = this.labelSmall,
     ): Typography =
-        Typography(
+        copy(
             displayLarge = displayLarge,
             displayMedium = displayMedium,
             displaySmall = displaySmall,
@@ -130,9 +428,25 @@
             bodySmall = bodySmall,
             labelLarge = labelLarge,
             labelMedium = labelMedium,
-            labelSmall = labelSmall
+            labelSmall = labelSmall,
+            displayLargeEmphasized = this.displayLargeEmphasized,
+            displayMediumEmphasized = this.displayMediumEmphasized,
+            displaySmallEmphasized = this.displaySmallEmphasized,
+            headlineLargeEmphasized = this.headlineLargeEmphasized,
+            headlineMediumEmphasized = this.headlineMediumEmphasized,
+            headlineSmallEmphasized = this.headlineSmallEmphasized,
+            titleLargeEmphasized = this.titleLargeEmphasized,
+            titleMediumEmphasized = this.titleMediumEmphasized,
+            titleSmallEmphasized = this.titleSmallEmphasized,
+            bodyLargeEmphasized = this.bodyLargeEmphasized,
+            bodyMediumEmphasized = this.bodyMediumEmphasized,
+            bodySmallEmphasized = this.bodySmallEmphasized,
+            labelLargeEmphasized = this.labelLargeEmphasized,
+            labelMediumEmphasized = this.labelMediumEmphasized,
+            labelSmallEmphasized = this.labelSmallEmphasized,
         )
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is Typography) return false
@@ -152,9 +466,25 @@
         if (labelLarge != other.labelLarge) return false
         if (labelMedium != other.labelMedium) return false
         if (labelSmall != other.labelSmall) return false
+        if (displayLargeEmphasized != other.displayLargeEmphasized) return false
+        if (displayMediumEmphasized != other.displayMediumEmphasized) return false
+        if (displaySmallEmphasized != other.displaySmallEmphasized) return false
+        if (headlineLargeEmphasized != other.headlineLargeEmphasized) return false
+        if (headlineMediumEmphasized != other.headlineMediumEmphasized) return false
+        if (headlineSmallEmphasized != other.headlineSmallEmphasized) return false
+        if (titleLargeEmphasized != other.titleLargeEmphasized) return false
+        if (titleMediumEmphasized != other.titleMediumEmphasized) return false
+        if (titleSmallEmphasized != other.titleSmallEmphasized) return false
+        if (bodyLargeEmphasized != other.bodyLargeEmphasized) return false
+        if (bodyMediumEmphasized != other.bodyMediumEmphasized) return false
+        if (bodySmallEmphasized != other.bodySmallEmphasized) return false
+        if (labelLargeEmphasized != other.labelLargeEmphasized) return false
+        if (labelMediumEmphasized != other.labelMediumEmphasized) return false
+        if (labelSmallEmphasized != other.labelSmallEmphasized) return false
         return true
     }
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun hashCode(): Int {
         var result = displayLarge.hashCode()
         result = 31 * result + displayMedium.hashCode()
@@ -171,9 +501,25 @@
         result = 31 * result + labelLarge.hashCode()
         result = 31 * result + labelMedium.hashCode()
         result = 31 * result + labelSmall.hashCode()
+        result = 31 * result + displayLargeEmphasized.hashCode()
+        result = 31 * result + displayMediumEmphasized.hashCode()
+        result = 31 * result + displaySmallEmphasized.hashCode()
+        result = 31 * result + headlineLargeEmphasized.hashCode()
+        result = 31 * result + headlineMediumEmphasized.hashCode()
+        result = 31 * result + headlineSmallEmphasized.hashCode()
+        result = 31 * result + titleLargeEmphasized.hashCode()
+        result = 31 * result + titleMediumEmphasized.hashCode()
+        result = 31 * result + titleSmallEmphasized.hashCode()
+        result = 31 * result + bodyLargeEmphasized.hashCode()
+        result = 31 * result + bodyMediumEmphasized.hashCode()
+        result = 31 * result + bodySmallEmphasized.hashCode()
+        result = 31 * result + labelLargeEmphasized.hashCode()
+        result = 31 * result + labelMediumEmphasized.hashCode()
+        result = 31 * result + labelSmallEmphasized.hashCode()
         return result
     }
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun toString(): String {
         return "Typography(displayLarge=$displayLarge, displayMedium=$displayMedium," +
             "displaySmall=$displaySmall, " +
@@ -181,11 +527,27 @@
             " headlineSmall=$headlineSmall, " +
             "titleLarge=$titleLarge, titleMedium=$titleMedium, titleSmall=$titleSmall, " +
             "bodyLarge=$bodyLarge, bodyMedium=$bodyMedium, bodySmall=$bodySmall, " +
-            "labelLarge=$labelLarge, labelMedium=$labelMedium, labelSmall=$labelSmall)"
+            "labelLarge=$labelLarge, labelMedium=$labelMedium, labelSmall=$labelSmall, " +
+            "displayLargeEmphasized=$displayLargeEmphasized, " +
+            "displayMediumEmphasized=$displayMediumEmphasized, " +
+            "displaySmallEmphasized=$displaySmallEmphasized, " +
+            "headlineLargeEmphasized=$headlineLargeEmphasized, " +
+            "headlineMediumEmphasized=$headlineMediumEmphasized, " +
+            "headlineSmallEmphasized=$headlineSmallEmphasized, " +
+            "titleLargeEmphasized=$titleLargeEmphasized, " +
+            "titleMediumEmphasized=$titleMediumEmphasized, " +
+            "titleSmallEmphasized=$titleSmallEmphasized, " +
+            "bodyLargeEmphasized=$bodyLargeEmphasized, " +
+            "bodyMediumEmphasized=$bodyMediumEmphasized, " +
+            "bodySmallEmphasized=$bodySmallEmphasized, " +
+            "labelLargeEmphasized=$labelLargeEmphasized, " +
+            "labelMediumEmphasized=$labelMediumEmphasized, " +
+            "labelSmallEmphasized=$labelSmallEmphasized)"
     }
 }
 
 /** Helper function for component typography tokens. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 private fun Typography.fromToken(value: TypographyKeyTokens): TextStyle {
     return when (value) {
         TypographyKeyTokens.DisplayLarge -> displayLarge
@@ -203,6 +565,21 @@
         TypographyKeyTokens.LabelLarge -> labelLarge
         TypographyKeyTokens.LabelMedium -> labelMedium
         TypographyKeyTokens.LabelSmall -> labelSmall
+        TypographyKeyTokens.DisplayLargeEmphasized -> displayLargeEmphasized
+        TypographyKeyTokens.DisplayMediumEmphasized -> displayMediumEmphasized
+        TypographyKeyTokens.DisplaySmallEmphasized -> displaySmallEmphasized
+        TypographyKeyTokens.HeadlineLargeEmphasized -> headlineLargeEmphasized
+        TypographyKeyTokens.HeadlineMediumEmphasized -> headlineMediumEmphasized
+        TypographyKeyTokens.HeadlineSmallEmphasized -> headlineSmallEmphasized
+        TypographyKeyTokens.TitleLargeEmphasized -> titleLargeEmphasized
+        TypographyKeyTokens.TitleMediumEmphasized -> titleMediumEmphasized
+        TypographyKeyTokens.TitleSmallEmphasized -> titleSmallEmphasized
+        TypographyKeyTokens.BodyLargeEmphasized -> bodyLargeEmphasized
+        TypographyKeyTokens.BodyMediumEmphasized -> bodyMediumEmphasized
+        TypographyKeyTokens.BodySmallEmphasized -> bodySmallEmphasized
+        TypographyKeyTokens.LabelLargeEmphasized -> labelLargeEmphasized
+        TypographyKeyTokens.LabelMediumEmphasized -> labelMediumEmphasized
+        TypographyKeyTokens.LabelSmallEmphasized -> labelSmallEmphasized
     }
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
index f3e8264..365ff16 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Canvas
@@ -41,11 +42,14 @@
 import androidx.compose.material3.internal.draggableAnchors
 import androidx.compose.material3.internal.getString
 import androidx.compose.material3.internal.systemBarsForVisualComponents
-import androidx.compose.material3.tokens.ColorSchemeKeyTokens
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
+import androidx.compose.material3.tokens.NavigationRailBaselineItemTokens
+import androidx.compose.material3.tokens.NavigationRailCollapsedTokens
+import androidx.compose.material3.tokens.NavigationRailColorTokens
+import androidx.compose.material3.tokens.NavigationRailExpandedTokens
+import androidx.compose.material3.tokens.NavigationRailHorizontalItemTokens
+import androidx.compose.material3.tokens.NavigationRailVerticalItemTokens
 import androidx.compose.material3.tokens.ScrimTokens
-import androidx.compose.material3.tokens.ShapeKeyTokens
-import androidx.compose.material3.tokens.TypographyKeyTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
@@ -57,7 +61,9 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.graphics.takeOrElse
@@ -84,6 +90,7 @@
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastSumBy
+import androidx.compose.ui.util.lerp
 import kotlin.jvm.JvmInline
 import kotlin.math.min
 import kotlinx.coroutines.launch
@@ -195,7 +202,7 @@
         )
     val itemVerticalSpacedBy by
         animateDpAsState(
-            targetValue = if (!expanded) VerticalPaddingBetweenTopIconItems else 0.dp,
+            targetValue = if (!expanded) NavigationRailCollapsedTokens.ItemVerticalSpace else 0.dp,
             animationSpec = animationSpec
         )
     val itemMarginStart by
@@ -283,7 +290,7 @@
                                                         ),
                                                     minHeight =
                                                         if (!expanded)
-                                                            WNRTopIconItemMinHeight.roundToPx()
+                                                            TopIconItemMinHeight.roundToPx()
                                                         else minimumA11ySize.roundToPx(),
                                                     maxWidth = itemMaxWidthConstraint,
                                                     maxHeight = looseConstraints.maxHeight,
@@ -441,15 +448,16 @@
     }
     val scope = rememberCoroutineScope()
     val predictiveBackProgress = remember { Animatable(initialValue = 0f) }
+    val predictiveBackState = remember { RailPredictiveBackState() }
 
     ModalWideNavigationRailDialog(
         properties = properties,
-        // TODO: Implement predictive back behavior.
         onDismissRequest = { scope.launch { animateToDismiss() } },
         onPredictiveBack = { backEvent ->
             scope.launch { predictiveBackProgress.snapTo(backEvent) }
         },
-        onPredictiveBackCancelled = { scope.launch { predictiveBackProgress.animateTo(0f) } }
+        onPredictiveBackCancelled = { scope.launch { predictiveBackProgress.animateTo(0f) } },
+        predictiveBackState = predictiveBackState
     ) {
         Box(modifier = Modifier.fillMaxSize().imePadding()) {
             Scrim(
@@ -458,6 +466,8 @@
                 visible = railState.targetValue != ModalExpandedNavigationRailValue.Closed
             )
             ModalWideNavigationRailContent(
+                predictiveBackProgress = predictiveBackProgress,
+                predictiveBackState = predictiveBackState,
                 settleToDismiss = settleToDismiss,
                 modifier = modifier,
                 railState = railState,
@@ -496,11 +506,11 @@
  * @param selected whether this item is selected
  * @param onClick called when this item is clicked
  * @param icon icon for this item, typically an [Icon]
+ * @param label text label for this item
  * @param modifier the [Modifier] to be applied to this item
  * @param enabled controls the enabled state of this item. When `false`, this component will not
  *   respond to user input, and it will appear visually disabled and disabled to accessibility
  *   services.
- * @param label text label for this item
  * @param badge optional badge to show on this item, typically a [Badge]
  * @param railExpanded whether the associated [WideNavigationRail] is expanded or collapsed
  * @param iconPosition the [NavigationItemIconPosition] for the icon
@@ -517,9 +527,9 @@
     selected: Boolean,
     onClick: () -> Unit,
     icon: @Composable () -> Unit,
+    label: @Composable (() -> Unit)?,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    label: @Composable (() -> Unit)? = null,
     badge: (@Composable () -> Unit)? = null,
     railExpanded: Boolean = false,
     iconPosition: NavigationItemIconPosition =
@@ -535,16 +545,18 @@
             selected = selected,
             onClick = onClick,
             icon = icon,
-            indicatorShape = ActiveIndicatorShape.value,
-            topIconIndicatorWidth = TopIconItemActiveIndicatorWidth,
-            topIconLabelTextStyle = TopIconLabelTextFont.value,
-            startIconLabelTextStyle = StartIconLabelTextFont.value,
+            indicatorShape = NavigationRailBaselineItemTokens.ActiveIndicatorShape.value,
+            topIconIndicatorWidth = NavigationRailVerticalItemTokens.ActiveIndicatorWidth,
+            topIconLabelTextStyle = NavigationRailVerticalItemTokens.LabelTextFont.value,
+            startIconLabelTextStyle = NavigationRailHorizontalItemTokens.LabelTextFont.value,
             topIconIndicatorHorizontalPadding = ItemTopIconIndicatorHorizontalPadding,
             topIconIndicatorVerticalPadding = ItemTopIconIndicatorVerticalPadding,
-            topIconIndicatorToLabelVerticalPadding = ItemTopIconIndicatorToLabelPadding,
-            startIconIndicatorHorizontalPadding = ItemStartIconIndicatorHorizontalPadding,
+            topIconIndicatorToLabelVerticalPadding =
+                NavigationRailVerticalItemTokens.IconLabelSpace,
+            startIconIndicatorHorizontalPadding =
+                NavigationRailHorizontalItemTokens.FullWidthLeadingSpace,
             startIconIndicatorVerticalPadding = ItemStartIconIndicatorVerticalPadding,
-            startIconToLabelHorizontalPadding = ItemStartIconToLabelPadding,
+            startIconToLabelHorizontalPadding = NavigationRailHorizontalItemTokens.IconLabelSpace,
             startIconItemPadding = ExpandedRailHorizontalItemPadding,
             colors = colors,
             modifier = modifier,
@@ -560,9 +572,9 @@
             selected = selected,
             onClick = onClick,
             icon = icon,
-            labelTextStyle = TopIconLabelTextFont.value,
-            indicatorShape = ActiveIndicatorShape.value,
-            indicatorWidth = TopIconItemActiveIndicatorWidth,
+            labelTextStyle = NavigationRailVerticalItemTokens.LabelTextFont.value,
+            indicatorShape = NavigationRailBaselineItemTokens.ActiveIndicatorShape.value,
+            indicatorWidth = NavigationRailVerticalItemTokens.ActiveIndicatorWidth,
             indicatorHorizontalPadding = WNRItemNoLabelIndicatorPadding,
             indicatorVerticalPadding = WNRItemNoLabelIndicatorPadding,
             indicatorToLabelVerticalPadding = 0.dp,
@@ -667,14 +679,12 @@
 @ExperimentalMaterial3ExpressiveApi
 object WideNavigationRailDefaults {
     /** Default container shape of a wide navigation rail. */
-    // TODO: Replace with token.
     val containerShape: Shape
-        @Composable get() = ShapeKeyTokens.CornerNone.value
+        @Composable get() = NavigationRailCollapsedTokens.ContainerShape.value
 
     /** Default container shape of a modal expanded navigation rail. */
-    // TODO: Replace with token.
     val modalContainerShape: Shape
-        @Composable get() = ShapeKeyTokens.CornerLarge.value
+        @Composable get() = NavigationRailExpandedTokens.ModalContainerShape.value
 
     /** Default arrangement for a wide navigation rail. */
     val Arrangement: WideNavigationRailArrangement
@@ -694,16 +704,18 @@
      */
     @Composable fun colors() = MaterialTheme.colorScheme.defaultWideWideNavigationRailColors
 
+    private val containerColor: Color
+        @Composable get() = NavigationRailCollapsedTokens.ContainerColor.value
+
     private val ColorScheme.defaultWideWideNavigationRailColors: WideNavigationRailColors
         @Composable
         get() {
             return mDefaultWideWideNavigationRailColorsCached
                 ?: WideNavigationRailColors(
-                        // TODO: Replace with tokens.
-                        containerColor = fromToken(ColorSchemeKeyTokens.Surface),
-                        contentColor = fromToken(ColorSchemeKeyTokens.OnSurfaceVariant),
+                        containerColor = containerColor,
+                        contentColor = contentColorFor(containerColor),
                         expandedModalContainerColor =
-                            fromToken(ColorSchemeKeyTokens.SurfaceContainer),
+                            fromToken(NavigationRailExpandedTokens.ModalContainerColor),
                         expandedModalScrimColor =
                             ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
                     )
@@ -731,15 +743,20 @@
         get() {
             return defaultWideNavigationRailItemColorsCached
                 ?: NavigationItemColors(
-                        selectedIconColor = fromToken(ActiveIconColor),
-                        selectedTextColor = fromToken(ActiveLabelTextColor),
-                        selectedIndicatorColor = fromToken(ActiveIndicatorColor),
-                        unselectedIconColor = fromToken(InactiveIconColor),
-                        unselectedTextColor = fromToken(InactiveLabelTextColor),
+                        selectedIconColor = fromToken(NavigationRailColorTokens.ItemActiveIcon),
+                        selectedTextColor =
+                            fromToken(NavigationRailColorTokens.ItemActiveLabelText),
+                        selectedIndicatorColor =
+                            fromToken(NavigationRailColorTokens.ItemActiveIndicator),
+                        unselectedIconColor = fromToken(NavigationRailColorTokens.ItemInactiveIcon),
+                        unselectedTextColor =
+                            fromToken(NavigationRailColorTokens.ItemInactiveLabelText),
                         disabledIconColor =
-                            fromToken(InactiveIconColor).copy(alpha = DisabledAlpha),
+                            fromToken(NavigationRailColorTokens.ItemInactiveIcon)
+                                .copy(alpha = DisabledAlpha),
                         disabledTextColor =
-                            fromToken(InactiveLabelTextColor).copy(alpha = DisabledAlpha),
+                            fromToken(NavigationRailColorTokens.ItemInactiveLabelText)
+                                .copy(alpha = DisabledAlpha),
                     )
                     .also { defaultWideNavigationRailItemColorsCached = it }
         }
@@ -769,14 +786,17 @@
     properties: ModalExpandedNavigationRailProperties,
     onPredictiveBack: (Float) -> Unit,
     onPredictiveBackCancelled: () -> Unit,
+    predictiveBackState: RailPredictiveBackState,
     content: @Composable () -> Unit
 )
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun ModalWideNavigationRailContent(
+    predictiveBackProgress: Animatable<Float, AnimationVector1D>,
+    predictiveBackState: RailPredictiveBackState,
     settleToDismiss: suspend (velocity: Float) -> Unit,
-    modifier: Modifier = Modifier,
+    modifier: Modifier,
     railState: ModalExpandedNavigationRailState,
     colors: WideNavigationRailColors,
     shape: Shape,
@@ -790,14 +810,32 @@
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
     val railPaneTitle = getString(string = Strings.WideNavigationRailPaneTitle)
 
-    Box(
+    Surface(
+        shape = shape,
+        color = colors.expandedModalContainerColor,
         modifier =
             modifier
-                .fillMaxHeight()
                 .widthIn(max = openModalRailMaxWidth)
+                .fillMaxHeight()
                 .semantics { paneTitle = railPaneTitle }
                 .graphicsLayer {
-                    // TODO: Implement predictive back behavior.
+                    val progress = predictiveBackProgress.value
+                    if (progress <= 0f) {
+                        return@graphicsLayer
+                    }
+                    val offset = railState.currentOffset
+                    val width = size.width
+                    if (!offset.isNaN() && !width.isNaN() && width != 0f) {
+                        // Apply the predictive back animation.
+                        scaleX =
+                            calculatePredictiveBackScaleX(
+                                progress,
+                                predictiveBackState.swipeEdgeMatchesRail
+                            )
+                        scaleY = calculatePredictiveBackScaleY(progress)
+                        transformOrigin =
+                            TransformOrigin(if (isRtl) 1f else 0f, PredictiveBackPivotFractionY)
+                    }
                 }
                 .draggableAnchors(railState.anchoredDraggableState, Orientation.Horizontal) {
                     railSize,
@@ -819,7 +857,26 @@
                 )
     ) {
         WideNavigationRailLayout(
-            modifier = modifier,
+            modifier =
+                Modifier.graphicsLayer {
+                    val progress = predictiveBackProgress.value
+                    if (progress <= 0) {
+                        return@graphicsLayer
+                    }
+                    // Preserve the original aspect ratio and alignment due to the predictive back
+                    // animation.
+                    val predictiveBackScaleX =
+                        calculatePredictiveBackScaleX(
+                            progress,
+                            predictiveBackState.swipeEdgeMatchesRail
+                        )
+                    val predictiveBackScaleY = calculatePredictiveBackScaleY(progress)
+                    scaleX =
+                        if (predictiveBackScaleX != 0f) predictiveBackScaleY / predictiveBackScaleX
+                        else 1f
+                    transformOrigin =
+                        TransformOrigin(if (isRtl) 0f else 1f, PredictiveBackPivotFractionY)
+                },
             expanded = true,
             shape = shape,
             colors = colors,
@@ -832,6 +889,32 @@
     }
 }
 
+private fun GraphicsLayerScope.calculatePredictiveBackScaleX(
+    progress: Float,
+    swipeEdgeMatchesRail: Boolean,
+): Float {
+    val width = size.width
+    return if (width.isNaN() || width == 0f) {
+        1f
+    } else {
+        val scaleXDirection = if (swipeEdgeMatchesRail) 1f else -1f
+        1f +
+            (scaleXDirection *
+                lerp(0f, min(PredictiveBackMaxScaleXDistance.toPx(), width), progress)) / width
+    }
+}
+
+private fun GraphicsLayerScope.calculatePredictiveBackScaleY(
+    progress: Float,
+): Float {
+    val height = size.height
+    return if (height.isNaN() || height == 0f) {
+        1f
+    } else {
+        1f - lerp(0f, min(PredictiveBackMaxScaleYDistance.toPx(), height), progress) / height
+    }
+}
+
 @Composable
 private fun Scrim(color: Color, onDismissRequest: suspend () -> Unit, visible: Boolean) {
     if (color.isSpecified) {
@@ -864,48 +947,34 @@
     }
 }
 
-private const val HeaderLayoutIdTag: String = "header"
+/*@VisibleForTesting*/
+internal val WNRItemNoLabelIndicatorPadding =
+    (NavigationRailVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationRailBaselineItemTokens.IconSize) / 2
 
-/* TODO: Replace below values with tokens. */
-private val IconSize = 24.0.dp
-private val TopIconItemActiveIndicatorWidth = 56.dp
-private val TopIconItemActiveIndicatorHeight = 32.dp
-private val StartIconItemActiveIndicatorHeight = 56.dp
-private val NoLabelItemActiveIndicatorHeight = 56.dp
-private val TopIconLabelTextFont = TypographyKeyTokens.LabelMedium
-private val StartIconLabelTextFont = TypographyKeyTokens.LabelLarge
-private val ActiveIndicatorShape = ShapeKeyTokens.CornerFull
-private val CollapsedRailWidth = 96.dp
-private val ExpandedRailMinWidth = 220.dp
-private val ExpandedRailMaxWidth = 360.dp
 private val ExpandedRailHorizontalItemPadding = 20.dp
-private val ItemStartIconIndicatorHorizontalPadding = 16.dp
-private val ItemStartIconToLabelPadding = 8.dp
-/*@VisibleForTesting*/
-internal val WNRTopIconItemMinHeight = 64.dp
-
-/*@VisibleForTesting*/
 // Vertical padding between the contents of the wide navigation rail and its top/bottom.
-internal val WNRVerticalPadding = 44.dp
-/*@VisibleForTesting*/
+private val WNRVerticalPadding = NavigationRailCollapsedTokens.TopSpace
 // Padding at the bottom of the rail's header. This padding will only be added when the header is
 // not null and the rail arrangement is Top.
-internal val WNRHeaderPadding: Dp = 40.dp
-/*@VisibleForTesting*/
-internal val WNRItemNoLabelIndicatorPadding = (NoLabelItemActiveIndicatorHeight - IconSize) / 2
-
-private val VerticalPaddingBetweenTopIconItems = 4.dp
-private val ItemMinWidth = CollapsedRailWidth
-private val ItemTopIconIndicatorVerticalPadding = (TopIconItemActiveIndicatorHeight - IconSize) / 2
-private val ItemTopIconIndicatorHorizontalPadding = (TopIconItemActiveIndicatorWidth - IconSize) / 2
+private val WNRHeaderPadding: Dp = NavigationRailBaselineItemTokens.HeaderSpaceMinimum
+private val CollapsedRailWidth = NavigationRailCollapsedTokens.ContainerWidth
+private val ExpandedRailMinWidth = NavigationRailExpandedTokens.ContainerWidthMinimum
+private val ExpandedRailMaxWidth = NavigationRailExpandedTokens.ContainerWidthMaximum
+private val ItemMinWidth = NavigationRailCollapsedTokens.ContainerWidth
+private val TopIconItemMinHeight = NavigationRailBaselineItemTokens.ContainerHeight
+private val ItemTopIconIndicatorVerticalPadding =
+    (NavigationRailVerticalItemTokens.ActiveIndicatorHeight -
+        NavigationRailBaselineItemTokens.IconSize) / 2
+private val ItemTopIconIndicatorHorizontalPadding =
+    (NavigationRailVerticalItemTokens.ActiveIndicatorWidth -
+        NavigationRailBaselineItemTokens.IconSize) / 2
 private val ItemStartIconIndicatorVerticalPadding =
-    (StartIconItemActiveIndicatorHeight - IconSize) / 2
-private val ItemTopIconIndicatorToLabelPadding: Dp = 4.dp
+    (NavigationRailHorizontalItemTokens.ActiveIndicatorHeight -
+        NavigationRailBaselineItemTokens.IconSize) / 2
+private val PredictiveBackMaxScaleXDistance = 24.dp
+private val PredictiveBackMaxScaleYDistance = 48.dp
 
-/* TODO: Replace below values with tokens. */
-// TODO: Update to OnSecondaryContainer once value matches Secondary.
-private val ActiveIconColor = ColorSchemeKeyTokens.Secondary
-private val ActiveLabelTextColor = ColorSchemeKeyTokens.Secondary
-private val ActiveIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
-private val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
-private val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+private const val PredictiveBackPivotFractionY = 0.5f
+private const val PredictiveBackPivotFractionYScaleDown = 0f
+private const val HeaderLayoutIdTag: String = "header"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
index 865f380..aa83fb2 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
@@ -22,9 +22,13 @@
 import androidx.compose.material3.internal.snapTo
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
@@ -197,3 +201,15 @@
         )
     }
 }
+
+@Stable
+internal class RailPredictiveBackState {
+    var swipeEdgeMatchesRail by mutableStateOf(true)
+
+    fun update(
+        isSwipeEdgeLeft: Boolean,
+        isRtl: Boolean,
+    ) {
+        swipeEdgeMatchesRail = (isSwipeEdgeLeft && !isRtl) || (!isSwipeEdgeLeft && isRtl)
+    }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt
new file mode 100644
index 0000000..bdecc5f
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationBarHorizontalItemTokens {
+    val ActiveIndicatorHeight = 40.0.dp
+    val ActiveIndicatorLeadingSpace = 16.0.dp
+    val ActiveIndicatorTrailingSpace = 16.0.dp
+    val IconSize = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
index be8d590..4842560 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
@@ -13,39 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_210
 // GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
 internal object NavigationBarTokens {
-    val ActiveFocusIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActiveHoverIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActiveIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
-    val ActiveIndicatorHeight = 32.0.dp
-    val ActiveIndicatorShape = ShapeKeyTokens.CornerFull
-    val ActiveIndicatorWidth = 64.0.dp
-    val ActiveLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActivePressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
     val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
     val ContainerElevation = ElevationTokens.Level2
-    val ContainerHeight = 80.0.dp
-    val ContainerShape = ShapeKeyTokens.CornerNone
-    val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
-    val IconSize = 24.0.dp
-    val InactiveFocusIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveHoverIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val InactivePressedIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val ContainerHeight = 64.0.dp
+    val ItemActiveIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemActiveIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
+    val ItemActiveIndicatorIconLabelSpace = 4.0.dp
+    val ItemActiveIndicatorShape = ShapeKeyTokens.CornerFull
+    val ItemActiveLabelTextColor = ColorSchemeKeyTokens.Secondary
+    val ItemBetweenSpace = 0.0.dp
+    val ItemInactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val ItemInactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val NavShape = ShapeKeyTokens.CornerNone
+    // TODO: Update this file to include the following missing tokens:
+    val TallContainerHeight = 80.0.dp
     val LabelTextFont = TypographyKeyTokens.LabelMedium
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt
new file mode 100644
index 0000000..5452b98
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationBarVerticalItemTokens {
+    val ActiveIndicatorHeight = 32.0.dp
+    val ActiveIndicatorWidth = 56.0.dp
+    val ContainerBetweenSpace = 6.0.dp
+    val IconSize = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt
new file mode 100644
index 0000000..144c675
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationRailBaselineItemTokens {
+    val ActiveIndicatorIconLabelSpace = 8.0.dp
+    val ActiveIndicatorLeadingSpace = 16.0.dp
+    val ActiveIndicatorShape = ShapeKeyTokens.CornerFull
+    val ActiveIndicatorTrailingSpace = 16.0.dp
+    val ContainerHeight = 64.0.dp
+    val ContainerShape = ShapeKeyTokens.CornerNone
+    val ContainerVerticalSpace = 6.0.dp
+    val HeaderSpaceMinimum = 40.0.dp
+    val IconSize = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt
new file mode 100644
index 0000000..4827019
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationRailCollapsedTokens {
+    val ContainerElevation = ElevationTokens.Level0
+    val ContainerShape = ShapeKeyTokens.CornerNone
+    val ContainerWidth = 96.0.dp
+    val ItemVerticalSpace = 4.0.dp
+    val TopSpace = 44.0.dp
+    // TODO: Update this file to include the following missing tokens:
+    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val NarrowContainerWidth = 80.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt
new file mode 100644
index 0000000..951345a
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+internal object NavigationRailColorTokens {
+    val ItemActiveFocusedStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemActiveHoveredStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemActiveIcon = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemActiveIndicator = ColorSchemeKeyTokens.SecondaryContainer
+    val ItemActiveLabelText = ColorSchemeKeyTokens.Secondary
+    val ItemActivePressedStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemInactiveFocusedStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemInactiveHoveredStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+    val ItemInactiveIcon = ColorSchemeKeyTokens.OnSurfaceVariant
+    val ItemInactiveLabelText = ColorSchemeKeyTokens.OnSurfaceVariant
+    val ItemInactivePressedStateLayer = ColorSchemeKeyTokens.OnSecondaryContainer
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt
new file mode 100644
index 0000000..9270c7e
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationRailExpandedTokens {
+    val ContainerElevation = ElevationTokens.Level0
+    val ContainerShape = ShapeKeyTokens.CornerNone
+    val ContainerWidthMaximum = 360.0.dp
+    val ContainerWidthMinimum = 220.0.dp
+    val ModalContainerElevation = ElevationTokens.Level2
+    val ModalContainerShape = ShapeKeyTokens.CornerLarge
+    val TopSpace = 44.0.dp
+    // TODO: Update this file to include the following missing tokens:
+    val ModalContainerColor = ColorSchemeKeyTokens.SurfaceContainer
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt
new file mode 100644
index 0000000..1c8d76a
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationRailHorizontalItemTokens {
+    val ActiveIndicatorHeight = 56.0.dp
+    val FullWidthLeadingSpace = 16.0.dp
+    val FullWidthTrailingSpace = 16.0.dp
+    val IconLabelSpace = 8.0.dp
+    val LeadingSpace = 16.0.dp
+    // TODO: Update this file to include the following missing tokens:
+    val LabelTextFont = TypographyKeyTokens.LabelLarge
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailTokens.kt
deleted file mode 100644
index 1c6758d..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailTokens.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object NavigationRailTokens {
-    val ActiveFocusIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActiveHoverIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActiveIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActiveIndicatorColor = ColorSchemeKeyTokens.SecondaryContainer
-    val ActiveIndicatorHeight = 32.0.dp
-    val ActiveIndicatorShape = ShapeKeyTokens.CornerFull
-    val ActiveIndicatorWidth = 56.0.dp
-    val ActiveLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ActivePressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-    val ActivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val ContainerColor = ColorSchemeKeyTokens.Surface
-    val ContainerElevation = ElevationTokens.Level0
-    val ContainerShape = ShapeKeyTokens.CornerNone
-    val ContainerWidth = 80.0.dp
-    val IconSize = 24.0.dp
-    val InactiveFocusIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveHoverIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val InactivePressedIconColor = ColorSchemeKeyTokens.OnSurface
-    val InactivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val LabelTextFont = TypographyKeyTokens.LabelMedium
-    val MenuFocusIconColor = ColorSchemeKeyTokens.OnSurface
-    val MenuHoverIconColor = ColorSchemeKeyTokens.OnSurface
-    val MenuIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val MenuIconSize = 24.0.dp
-    val MenuPressedIconColor = ColorSchemeKeyTokens.OnSurface
-    val NoLabelActiveIndicatorHeight = 56.0.dp
-    val NoLabelActiveIndicatorShape = ShapeKeyTokens.CornerFull
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt
new file mode 100644
index 0000000..a87af2b
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// TO MODIFY THIS FILE UPDATE:
+//   ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
+//
+// Design System: sandbox
+// Version: v0_9_0
+// Audience: 1p
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object NavigationRailVerticalItemTokens {
+    val ActiveIndicatorHeight = 32.0.dp
+    val ActiveIndicatorWidth = 56.0.dp
+    val IconLabelSpace = 4.0.dp
+    val LeadingSpace = 16.0.dp
+    val TrailingSpace = 16.0.dp
+    // TODO: Update this file to include the following missing tokens:
+    val LabelTextFont = TypographyKeyTokens.LabelMedium
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScaleTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScaleTokens.kt
index 0073539..997e3ed 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScaleTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypeScaleTokens.kt
@@ -96,4 +96,80 @@
     val TitleSmallSize = 14.sp
     val TitleSmallTracking = 0.1.sp
     val TitleSmallWeight = TypefaceTokens.WeightMedium
+    // TODO update with the generated tokens once available
+    val BodyLargeEmphasizedFont = TypefaceTokens.Plain
+    val BodyLargeEmphasizedLineHeight = 24.0.sp
+    val BodyLargeEmphasizedSize = 16.sp
+    val BodyLargeEmphasizedTracking = 0.15.sp
+    val BodyLargeEmphasizedWeight = TypefaceTokens.WeightMedium
+    val BodyMediumEmphasizedFont = TypefaceTokens.Plain
+    val BodyMediumEmphasizedLineHeight = 20.0.sp
+    val BodyMediumEmphasizedSize = 14.sp
+    val BodyMediumEmphasizedTracking = 0.25.sp
+    val BodyMediumEmphasizedWeight = TypefaceTokens.WeightMedium
+    val BodySmallEmphasizedFont = TypefaceTokens.Plain
+    val BodySmallEmphasizedLineHeight = 16.0.sp
+    val BodySmallEmphasizedSize = 12.sp
+    val BodySmallEmphasizedTracking = 0.4.sp
+    val BodySmallEmphasizedWeight = TypefaceTokens.WeightMedium
+    val DisplayLargeEmphasizedFont = TypefaceTokens.Brand
+    val DisplayLargeEmphasizedLineHeight = 64.0.sp
+    val DisplayLargeEmphasizedSize = 57.sp
+    val DisplayLargeEmphasizedTracking = 0.sp
+    val DisplayLargeEmphasizedWeight = TypefaceTokens.WeightMedium
+    val DisplayMediumEmphasizedFont = TypefaceTokens.Brand
+    val DisplayMediumEmphasizedLineHeight = 52.0.sp
+    val DisplayMediumEmphasizedSize = 45.sp
+    val DisplayMediumEmphasizedTracking = 0.sp
+    val DisplayMediumEmphasizedWeight = TypefaceTokens.WeightMedium
+    val DisplaySmallEmphasizedFont = TypefaceTokens.Brand
+    val DisplaySmallEmphasizedLineHeight = 44.0.sp
+    val DisplaySmallEmphasizedSize = 36.sp
+    val DisplaySmallEmphasizedTracking = 0.sp
+    val DisplaySmallEmphasizedWeight = TypefaceTokens.WeightMedium
+    val HeadlineLargeEmphasizedFont = TypefaceTokens.Brand
+    val HeadlineLargeEmphasizedLineHeight = 40.0.sp
+    val HeadlineLargeEmphasizedSize = 32.sp
+    val HeadlineLargeEmphasizedTracking = 0.sp
+    val HeadlineLargeEmphasizedWeight = TypefaceTokens.WeightMedium
+    val HeadlineMediumEmphasizedFont = TypefaceTokens.Brand
+    val HeadlineMediumEmphasizedLineHeight = 36.0.sp
+    val HeadlineMediumEmphasizedSize = 28.sp
+    val HeadlineMediumEmphasizedTracking = 0.sp
+    val HeadlineMediumEmphasizedWeight = TypefaceTokens.WeightMedium
+    val HeadlineSmallEmphasizedFont = TypefaceTokens.Brand
+    val HeadlineSmallEmphasizedLineHeight = 32.0.sp
+    val HeadlineSmallEmphasizedSize = 24.sp
+    val HeadlineSmallEmphasizedTracking = 0.sp
+    val HeadlineSmallEmphasizedWeight = TypefaceTokens.WeightMedium
+    val LabelLargeEmphasizedFont = TypefaceTokens.Plain
+    val LabelLargeEmphasizedLineHeight = 20.0.sp
+    val LabelLargeEmphasizedSize = 14.sp
+    val LabelLargeEmphasizedTracking = 0.1.sp
+    val LabelLargeEmphasizedWeight = TypefaceTokens.WeightBold
+    val LabelMediumEmphasizedFont = TypefaceTokens.Plain
+    val LabelMediumEmphasizedLineHeight = 16.0.sp
+    val LabelMediumEmphasizedSize = 12.sp
+    val LabelMediumEmphasizedTracking = 0.5.sp
+    val LabelMediumEmphasizedWeight = TypefaceTokens.WeightBold
+    val LabelSmallEmphasizedFont = TypefaceTokens.Plain
+    val LabelSmallEmphasizedLineHeight = 16.0.sp
+    val LabelSmallEmphasizedSize = 11.sp
+    val LabelSmallEmphasizedTracking = 0.5.sp
+    val LabelSmallEmphasizedWeight = TypefaceTokens.WeightBold
+    val TitleLargeEmphasizedFont = TypefaceTokens.Brand
+    val TitleLargeEmphasizedLineHeight = 28.0.sp
+    val TitleLargeEmphasizedSize = 22.sp
+    val TitleLargeEmphasizedTracking = 0.sp
+    val TitleLargeEmphasizedWeight = TypefaceTokens.WeightMedium
+    val TitleMediumEmphasizedFont = TypefaceTokens.Plain
+    val TitleMediumEmphasizedLineHeight = 24.0.sp
+    val TitleMediumEmphasizedSize = 16.sp
+    val TitleMediumEmphasizedTracking = 0.15.sp
+    val TitleMediumEmphasizedWeight = TypefaceTokens.WeightBold
+    val TitleSmallEmphasizedFont = TypefaceTokens.Plain
+    val TitleSmallEmphasizedLineHeight = 20.0.sp
+    val TitleSmallEmphasizedSize = 14.sp
+    val TitleSmallEmphasizedTracking = 0.1.sp
+    val TitleSmallEmphasizedWeight = TypefaceTokens.WeightBold
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyKeyTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyKeyTokens.kt
index 0e58763..fd559d8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyKeyTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyKeyTokens.kt
@@ -34,4 +34,20 @@
     TitleLarge,
     TitleMedium,
     TitleSmall,
+    // TODO update with the generated tokens once available
+    BodyLargeEmphasized,
+    BodyMediumEmphasized,
+    BodySmallEmphasized,
+    DisplayLargeEmphasized,
+    DisplayMediumEmphasized,
+    DisplaySmallEmphasized,
+    HeadlineLargeEmphasized,
+    HeadlineMediumEmphasized,
+    HeadlineSmallEmphasized,
+    LabelLargeEmphasized,
+    LabelMediumEmphasized,
+    LabelSmallEmphasized,
+    TitleLargeEmphasized,
+    TitleMediumEmphasized,
+    TitleSmallEmphasized,
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
index 46d7643..3d12ae9 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
@@ -143,6 +143,127 @@
             lineHeight = TypeScaleTokens.TitleSmallLineHeight,
             letterSpacing = TypeScaleTokens.TitleSmallTracking,
         )
+    // TODO update with the generated tokens once available
+    val BodyLargeEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.BodyLargeEmphasizedFont,
+            fontWeight = TypeScaleTokens.BodyLargeEmphasizedWeight,
+            fontSize = TypeScaleTokens.BodyLargeEmphasizedSize,
+            lineHeight = TypeScaleTokens.BodyLargeEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.BodyLargeEmphasizedTracking,
+        )
+    val BodyMediumEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.BodyMediumEmphasizedFont,
+            fontWeight = TypeScaleTokens.BodyMediumEmphasizedWeight,
+            fontSize = TypeScaleTokens.BodyMediumEmphasizedSize,
+            lineHeight = TypeScaleTokens.BodyMediumEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.BodyMediumEmphasizedTracking,
+        )
+    val BodySmallEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.BodySmallEmphasizedFont,
+            fontWeight = TypeScaleTokens.BodySmallEmphasizedWeight,
+            fontSize = TypeScaleTokens.BodySmallEmphasizedSize,
+            lineHeight = TypeScaleTokens.BodySmallEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.BodySmallEmphasizedTracking,
+        )
+    val DisplayLargeEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.DisplayLargeEmphasizedFont,
+            fontWeight = TypeScaleTokens.DisplayLargeEmphasizedWeight,
+            fontSize = TypeScaleTokens.DisplayLargeEmphasizedSize,
+            lineHeight = TypeScaleTokens.DisplayLargeEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.DisplayLargeEmphasizedTracking,
+        )
+    val DisplayMediumEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.DisplayMediumEmphasizedFont,
+            fontWeight = TypeScaleTokens.DisplayMediumEmphasizedWeight,
+            fontSize = TypeScaleTokens.DisplayMediumEmphasizedSize,
+            lineHeight = TypeScaleTokens.DisplayMediumEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.DisplayMediumEmphasizedTracking,
+        )
+    val DisplaySmallEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.DisplaySmallEmphasizedFont,
+            fontWeight = TypeScaleTokens.DisplaySmallEmphasizedWeight,
+            fontSize = TypeScaleTokens.DisplaySmallEmphasizedSize,
+            lineHeight = TypeScaleTokens.DisplaySmallEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.DisplaySmallEmphasizedTracking,
+        )
+    val HeadlineLargeEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.HeadlineLargeEmphasizedFont,
+            fontWeight = TypeScaleTokens.HeadlineLargeEmphasizedWeight,
+            fontSize = TypeScaleTokens.HeadlineLargeEmphasizedSize,
+            lineHeight = TypeScaleTokens.HeadlineLargeEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineLargeEmphasizedTracking,
+        )
+    val HeadlineMediumEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.HeadlineMediumEmphasizedFont,
+            fontWeight = TypeScaleTokens.HeadlineMediumEmphasizedWeight,
+            fontSize = TypeScaleTokens.HeadlineMediumEmphasizedSize,
+            lineHeight = TypeScaleTokens.HeadlineMediumEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineMediumEmphasizedTracking,
+        )
+    val HeadlineSmallEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.HeadlineSmallEmphasizedFont,
+            fontWeight = TypeScaleTokens.HeadlineSmallEmphasizedWeight,
+            fontSize = TypeScaleTokens.HeadlineSmallEmphasizedSize,
+            lineHeight = TypeScaleTokens.HeadlineSmallEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineSmallEmphasizedTracking,
+        )
+    val LabelLargeEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.LabelLargeEmphasizedFont,
+            fontWeight = TypeScaleTokens.LabelLargeEmphasizedWeight,
+            fontSize = TypeScaleTokens.LabelLargeEmphasizedSize,
+            lineHeight = TypeScaleTokens.LabelLargeEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.LabelLargeEmphasizedTracking,
+        )
+    val LabelMediumEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.LabelMediumEmphasizedFont,
+            fontWeight = TypeScaleTokens.LabelMediumEmphasizedWeight,
+            fontSize = TypeScaleTokens.LabelMediumEmphasizedSize,
+            lineHeight = TypeScaleTokens.LabelMediumEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.LabelMediumEmphasizedTracking,
+        )
+    val LabelSmallEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.LabelSmallEmphasizedFont,
+            fontWeight = TypeScaleTokens.LabelSmallEmphasizedWeight,
+            fontSize = TypeScaleTokens.LabelSmallEmphasizedSize,
+            lineHeight = TypeScaleTokens.LabelSmallEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.LabelSmallEmphasizedTracking,
+        )
+    val TitleLargeEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.TitleLargeEmphasizedFont,
+            fontWeight = TypeScaleTokens.TitleLargeEmphasizedWeight,
+            fontSize = TypeScaleTokens.TitleLargeEmphasizedSize,
+            lineHeight = TypeScaleTokens.TitleLargeEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.TitleLargeEmphasizedTracking,
+        )
+    val TitleMediumEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.TitleMediumEmphasizedFont,
+            fontWeight = TypeScaleTokens.TitleMediumEmphasizedWeight,
+            fontSize = TypeScaleTokens.TitleMediumEmphasizedSize,
+            lineHeight = TypeScaleTokens.TitleMediumEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.TitleMediumEmphasizedTracking,
+        )
+    val TitleSmallEmphasized =
+        DefaultTextStyle.copy(
+            fontFamily = TypeScaleTokens.TitleSmallEmphasizedFont,
+            fontWeight = TypeScaleTokens.TitleSmallEmphasizedWeight,
+            fontSize = TypeScaleTokens.TitleSmallEmphasizedSize,
+            lineHeight = TypeScaleTokens.TitleSmallEmphasizedLineHeight,
+            letterSpacing = TypeScaleTokens.TitleSmallEmphasizedTracking,
+        )
 }
 
 internal val DefaultLineHeightStyle =
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/WideNavigationRail.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/WideNavigationRail.commonStubs.kt
index 8d65b88..7c960c0 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/WideNavigationRail.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/WideNavigationRail.commonStubs.kt
@@ -39,5 +39,6 @@
     properties: ModalExpandedNavigationRailProperties,
     onPredictiveBack: (Float) -> Unit,
     onPredictiveBackCancelled: () -> Unit,
+    predictiveBackState: RailPredictiveBackState,
     content: @Composable () -> Unit
 ): Unit = implementedInJetBrainsFork()
diff --git a/compose/runtime/runtime-saveable/src/androidInstrumentedTest/kotlin/androidx/compose/runtime/saveable/RememberSaveableTest.kt b/compose/runtime/runtime-saveable/src/androidInstrumentedTest/kotlin/androidx/compose/runtime/saveable/RememberSaveableTest.kt
index 58f4447..611825f 100644
--- a/compose/runtime/runtime-saveable/src/androidInstrumentedTest/kotlin/androidx/compose/runtime/saveable/RememberSaveableTest.kt
+++ b/compose/runtime/runtime-saveable/src/androidInstrumentedTest/kotlin/androidx/compose/runtime/saveable/RememberSaveableTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -184,7 +185,7 @@
             )
 
         rule.setContent {
-            WrapRegistry(wrap = { registryFactory(it) }) {
+            WrapRegistry(wrap = registryFactory) {
                 val v = rememberSaveable { 1 }
                 assertEquals(1, v)
             }
@@ -280,25 +281,25 @@
         var doEmit by mutableStateOf(true)
         var onUnregisterCalled = false
 
-        rule.setContent {
-            WrapRegistry(
-                wrap = {
-                    object : DelegateRegistry(it) {
-                        override fun registerProvider(
-                            key: String,
-                            valueProvider: () -> Any?
-                        ): SaveableStateRegistry.Entry {
-                            val entry = super.registerProvider(key, valueProvider)
-                            return object : SaveableStateRegistry.Entry {
-                                override fun unregister() {
-                                    onUnregisterCalled = true
-                                    entry.unregister()
-                                }
-                            }
+        // Define the lambda here to avoid it changing in recompositions
+        val wrapRegistryLambda: (SaveableStateRegistry) -> SaveableStateRegistry = {
+            object : DelegateRegistry(it) {
+                override fun registerProvider(
+                    key: String,
+                    valueProvider: () -> Any?
+                ): SaveableStateRegistry.Entry {
+                    val entry = super.registerProvider(key, valueProvider)
+                    return object : SaveableStateRegistry.Entry {
+                        override fun unregister() {
+                            onUnregisterCalled = true
+                            entry.unregister()
                         }
                     }
                 }
-            ) {
+            }
+        }
+        rule.setContent {
+            WrapRegistry(wrap = wrapRegistryLambda) {
                 if (doEmit) {
                     rememberSaveable { 1 }
                 }
@@ -428,13 +429,12 @@
 
 @Composable
 private fun WrapRegistry(
-    wrap: @Composable (SaveableStateRegistry) -> SaveableStateRegistry,
+    wrap: (SaveableStateRegistry) -> SaveableStateRegistry,
     content: @Composable () -> Unit
 ) {
-    CompositionLocalProvider(
-        LocalSaveableStateRegistry provides wrap(LocalSaveableStateRegistry.current!!),
-        content = content
-    )
+    val original = LocalSaveableStateRegistry.current!!
+    val wrapped = remember(original, wrap) { wrap(original) }
+    CompositionLocalProvider(LocalSaveableStateRegistry provides wrapped, content = content)
 }
 
 private open class DelegateRegistry(original: SaveableStateRegistry) :
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 77e4f1f..e2efd82 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -666,6 +666,26 @@
         }
     }
 
+    // Drain the modification out of the normal recordModificationsOf(), composition() cycle.
+    // This avoids the checks to make sure the two calls are called in order.
+    @Suppress("UNCHECKED_CAST")
+    private fun drainPendingModificationsOutOfBandLocked() {
+        when (val toRecord = pendingModifications.getAndSet(emptySet<Any>())) {
+            PendingApplyNoModifications,
+            null -> {
+                // No work to do
+            }
+            is Set<*> -> {
+                addPendingInvalidationsLocked(toRecord as Set<Any>, forgetConditionalScopes = false)
+            }
+            is Array<*> ->
+                for (changed in toRecord as Array<Set<Any>>) {
+                    addPendingInvalidationsLocked(changed, forgetConditionalScopes = false)
+                }
+            else -> composeRuntimeError("corrupt pendingModifications drain: $pendingModifications")
+        }
+    }
+
     override fun composeContent(content: @Composable () -> Unit) {
         // TODO: This should raise a signal to any currently running recompose calls
         //   to halt and return
@@ -690,7 +710,7 @@
 
     internal fun updateMovingInvalidations() {
         synchronized(lock) {
-            drainPendingModificationsForCompositionLocked()
+            drainPendingModificationsOutOfBandLocked()
             guardInvalidationsLocked { invalidations ->
                 composer.updateComposerInvalidations(invalidations)
             }
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
index fc898ae..1f6e21b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
@@ -854,6 +854,34 @@
         assertEquals(1, rememberedState.forgottenCount)
         assertEquals(0, rememberedState.abandonCount)
     }
+
+    @Test
+    fun reusableContentHost_movableContent(): Unit = compositionTest {
+        var active by mutableStateOf(true)
+        var state by mutableIntStateOf(0)
+        var subcomposition: Composition? = null
+        val movableContent = movableContentOf { Text("State: $state") }
+
+        compose {
+            if (active) {
+                val context = rememberCompositionContext()
+                if (subcomposition == null) {
+                    subcomposition =
+                        Composition(ViewApplier(root), context).apply {
+                            setContent { movableContent() }
+                        }
+                }
+            }
+        }
+
+        state++
+        active = false
+        advance()
+
+        // Validate that detaching the composition will not leave the composition in an invalid
+        // state.
+        subcomposition?.setContent { Text("This is a test") }
+    }
 }
 
 private fun View.findTextWith(contains: String) = find {
diff --git a/compose/ui/ui-geometry/api/current.txt b/compose/ui/ui-geometry/api/current.txt
index cf25073d..c26e3c6 100644
--- a/compose/ui/ui-geometry/api/current.txt
+++ b/compose/ui/ui-geometry/api/current.txt
@@ -65,16 +65,16 @@
     method @androidx.compose.runtime.Stable public operator long div(float operand);
     method @androidx.compose.runtime.Stable public float getDistance();
     method @androidx.compose.runtime.Stable public float getDistanceSquared();
-    method public float getX();
-    method public float getY();
-    method @androidx.compose.runtime.Stable public boolean isValid();
+    method public inline float getX();
+    method public inline float getY();
+    method @androidx.compose.runtime.Stable public inline boolean isValid();
     method @androidx.compose.runtime.Stable public operator long minus(long other);
     method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property @androidx.compose.runtime.Stable public final float x;
-    property @androidx.compose.runtime.Stable public final float y;
+    method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
+    property @androidx.compose.runtime.Stable public final inline float x;
+    property @androidx.compose.runtime.Stable public final inline float y;
     field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
   }
 
@@ -89,9 +89,9 @@
 
   public final class OffsetKt {
     method @androidx.compose.runtime.Stable public static long Offset(float x, float y);
-    method public static boolean isFinite(long);
-    method public static boolean isSpecified(long);
-    method public static boolean isUnspecified(long);
+    method public static inline boolean isFinite(long);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.geometry.Offset> block);
   }
@@ -112,7 +112,7 @@
     method public long getCenter();
     method public long getCenterLeft();
     method public long getCenterRight();
-    method public float getHeight();
+    method public inline float getHeight();
     method public float getLeft();
     method public float getMaxDimension();
     method public float getMinDimension();
@@ -122,7 +122,7 @@
     method public long getTopCenter();
     method public long getTopLeft();
     method public long getTopRight();
-    method public float getWidth();
+    method public inline float getWidth();
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect inflate(float delta);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect intersect(androidx.compose.ui.geometry.Rect other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect intersect(float otherLeft, float otherTop, float otherRight, float otherBottom);
@@ -139,7 +139,7 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final inline float height;
     property @androidx.compose.runtime.Stable public final boolean isEmpty;
     property @androidx.compose.runtime.Stable public final boolean isFinite;
     property @androidx.compose.runtime.Stable public final boolean isInfinite;
@@ -152,7 +152,7 @@
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property @androidx.compose.runtime.Stable public final float width;
+    property @androidx.compose.runtime.Stable public final inline float width;
     field public static final androidx.compose.ui.geometry.Rect.Companion Companion;
   }
 
@@ -236,16 +236,16 @@
     method @androidx.compose.runtime.Stable public inline operator float component2();
     method public long copy(optional float width, optional float height);
     method @androidx.compose.runtime.Stable public operator long div(float operand);
-    method public float getHeight();
+    method public inline float getHeight();
     method public float getMaxDimension();
     method public float getMinDimension();
-    method public float getWidth();
+    method public inline float getWidth();
     method @androidx.compose.runtime.Stable public boolean isEmpty();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final inline float height;
     property @androidx.compose.runtime.Stable public final float maxDimension;
     property @androidx.compose.runtime.Stable public final float minDimension;
-    property @androidx.compose.runtime.Stable public final float width;
+    property @androidx.compose.runtime.Stable public final inline float width;
     field public static final androidx.compose.ui.geometry.Size.Companion Companion;
   }
 
@@ -257,7 +257,7 @@
   }
 
   public final class SizeKt {
-    method @androidx.compose.runtime.Stable public static long Size(float width, float height);
+    method @androidx.compose.runtime.Stable public static inline long Size(float width, float height);
     method public static long getCenter(long);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
diff --git a/compose/ui/ui-geometry/api/restricted_current.txt b/compose/ui/ui-geometry/api/restricted_current.txt
index cf25073d..8616a99 100644
--- a/compose/ui/ui-geometry/api/restricted_current.txt
+++ b/compose/ui/ui-geometry/api/restricted_current.txt
@@ -27,6 +27,17 @@
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
   }
 
+  public final class InlineClassHelperKt {
+    field @kotlin.PublishedApi internal static final long DualFirstNaN = 9187343246269874177L; // 0x7f8000017f800001L
+    field @kotlin.PublishedApi internal static final long DualFloatInfinityBase = 9187343241974906880L; // 0x7f8000007f800000L
+    field @kotlin.PublishedApi internal static final long DualFloatSignBit = -9223372034707292160L; // 0x8000000080000000L
+    field @kotlin.PublishedApi internal static final long DualUnsignedFloatMask = 9223372034707292159L; // 0x7fffffff7fffffffL
+    field @kotlin.PublishedApi internal static final int FloatInfinityBase = 2139095040; // 0x7f800000
+    field @kotlin.PublishedApi internal static final long Uint64High32 = -9223372034707292160L; // 0x8000000080000000L
+    field @kotlin.PublishedApi internal static final long Uint64Low32 = 4294967297L; // 0x100000001L
+    field @kotlin.PublishedApi internal static final long UnspecifiedPackedFloats = 9205357640488583168L; // 0x7fc000007fc00000L
+  }
+
   public final class MutableRect {
     ctor public MutableRect(float left, float top, float right, float bottom);
     method public operator boolean contains(long offset);
@@ -59,22 +70,23 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Offset {
+    ctor @kotlin.PublishedApi internal Offset(@kotlin.PublishedApi long packedValue);
     method @androidx.compose.runtime.Stable public inline operator float component1();
     method @androidx.compose.runtime.Stable public inline operator float component2();
     method public long copy(optional float x, optional float y);
     method @androidx.compose.runtime.Stable public operator long div(float operand);
     method @androidx.compose.runtime.Stable public float getDistance();
     method @androidx.compose.runtime.Stable public float getDistanceSquared();
-    method public float getX();
-    method public float getY();
-    method @androidx.compose.runtime.Stable public boolean isValid();
+    method public inline float getX();
+    method public inline float getY();
+    method @androidx.compose.runtime.Stable public inline boolean isValid();
     method @androidx.compose.runtime.Stable public operator long minus(long other);
     method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property @androidx.compose.runtime.Stable public final float x;
-    property @androidx.compose.runtime.Stable public final float y;
+    method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
+    property @androidx.compose.runtime.Stable public final inline float x;
+    property @androidx.compose.runtime.Stable public final inline float y;
     field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
   }
 
@@ -89,9 +101,9 @@
 
   public final class OffsetKt {
     method @androidx.compose.runtime.Stable public static long Offset(float x, float y);
-    method public static boolean isFinite(long);
-    method public static boolean isSpecified(long);
-    method public static boolean isUnspecified(long);
+    method public static inline boolean isFinite(long);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.geometry.Offset> block);
   }
@@ -112,7 +124,7 @@
     method public long getCenter();
     method public long getCenterLeft();
     method public long getCenterRight();
-    method public float getHeight();
+    method public inline float getHeight();
     method public float getLeft();
     method public float getMaxDimension();
     method public float getMinDimension();
@@ -122,7 +134,7 @@
     method public long getTopCenter();
     method public long getTopLeft();
     method public long getTopRight();
-    method public float getWidth();
+    method public inline float getWidth();
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect inflate(float delta);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect intersect(androidx.compose.ui.geometry.Rect other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect intersect(float otherLeft, float otherTop, float otherRight, float otherBottom);
@@ -139,7 +151,7 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final inline float height;
     property @androidx.compose.runtime.Stable public final boolean isEmpty;
     property @androidx.compose.runtime.Stable public final boolean isFinite;
     property @androidx.compose.runtime.Stable public final boolean isInfinite;
@@ -152,7 +164,7 @@
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property @androidx.compose.runtime.Stable public final float width;
+    property @androidx.compose.runtime.Stable public final inline float width;
     field public static final androidx.compose.ui.geometry.Rect.Companion Companion;
   }
 
@@ -232,20 +244,21 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Size {
+    ctor @kotlin.PublishedApi internal Size(@kotlin.PublishedApi long packedValue);
     method @androidx.compose.runtime.Stable public inline operator float component1();
     method @androidx.compose.runtime.Stable public inline operator float component2();
     method public long copy(optional float width, optional float height);
     method @androidx.compose.runtime.Stable public operator long div(float operand);
-    method public float getHeight();
+    method public inline float getHeight();
     method public float getMaxDimension();
     method public float getMinDimension();
-    method public float getWidth();
+    method public inline float getWidth();
     method @androidx.compose.runtime.Stable public boolean isEmpty();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final inline float height;
     property @androidx.compose.runtime.Stable public final float maxDimension;
     property @androidx.compose.runtime.Stable public final float minDimension;
-    property @androidx.compose.runtime.Stable public final float width;
+    property @androidx.compose.runtime.Stable public final inline float width;
     field public static final androidx.compose.ui.geometry.Size.Companion Companion;
   }
 
@@ -257,7 +270,7 @@
   }
 
   public final class SizeKt {
-    method @androidx.compose.runtime.Stable public static long Size(float width, float height);
+    method @androidx.compose.runtime.Stable public static inline long Size(float width, float height);
     method public static long getCenter(long);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
diff --git a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
index b2dbb7b..19d9d44 100644
--- a/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
+++ b/compose/ui/ui-geometry/src/androidUnitTest/kotlin/androidx/compose/ui/geometry/SizeTest.kt
@@ -16,11 +16,9 @@
 
 package androidx.compose.ui.geometry
 
-import kotlin.test.assertFails
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -84,47 +82,6 @@
     }
 
     @Test
-    fun testUnspecifiedWidthQueryThrows() {
-        try {
-            Size.Unspecified.width
-            fail("Size.Unspecified.width is not allowed")
-        } catch (t: Throwable) {
-            // no-op
-        }
-    }
-
-    @Test
-    fun testUnspecifiedHeightQueryThrows() {
-        try {
-            Size.Unspecified.height
-            fail("Size.Unspecified.height is not allowed")
-        } catch (t: Throwable) {
-            // no-op
-        }
-    }
-
-    @Test
-    fun testUnspecifiedCopyThrows() {
-        try {
-            Size.Unspecified.copy(width = 100f)
-            Size.Unspecified.copy(height = 70f)
-            fail("Size.Unspecified.copy is not allowed")
-        } catch (t: Throwable) {
-            // no-op
-        }
-    }
-
-    @Test
-    fun testUnspecifiedComponentAssignmentThrows() {
-        try {
-            val (_, _) = Size.Unspecified
-            fail("Size.Unspecified component assignment is not allowed")
-        } catch (t: Throwable) {
-            // no-op
-        }
-    }
-
-    @Test
     fun testSizeLerp() {
         val size1 = Size(100f, 200f)
         val size2 = Size(300f, 500f)
@@ -181,6 +138,6 @@
         assertTrue(Size(Float.NEGATIVE_INFINITY, 20.0f).isEmpty())
         assertTrue(Size(10.0f, Float.NEGATIVE_INFINITY).isEmpty())
 
-        assertFails { Size.Unspecified.isEmpty() }
+        assertTrue(Size.Unspecified.isEmpty())
     }
 }
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
index 788877c..8338bd2 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/InlineClassHelper.kt
@@ -16,41 +16,27 @@
 
 package androidx.compose.ui.geometry
 
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
 // Masks everything but the sign bit
-internal const val DualUnsignedFloatMask = 0x7fffffff_7fffffffL
+@PublishedApi internal const val DualUnsignedFloatMask = 0x7fffffff_7fffffffL
 
 // Any value greater than this is a NaN
-internal const val FloatInfinityBase = 0x7f800000L
-internal const val DualFloatInfinityBase = 0x7f800000_7f800000L
+@PublishedApi internal const val FloatInfinityBase = 0x7f800000
+
+// Same as above, but for floats packed in a Long
+@PublishedApi internal const val DualFloatInfinityBase = 0x7f800000_7f800000L
 
 // Same as Offset/Size.Unspecified.packedValue, but avoids a getstatic
-internal const val UnspecifiedPackedFloats = 0x7fc00000_7fc00000L // NaN_NaN
+@PublishedApi internal const val UnspecifiedPackedFloats = 0x7fc00000_7fc00000L // NaN_NaN
 
 // 0x80000000_80000000UL.toLong() but expressed as a const value
 // Mask for the sign bit of the two floats packed in a long
-internal const val DualFloatSignBit = -0x7fffffff_80000000L
+@PublishedApi internal const val DualFloatSignBit = -0x7fffffff_80000000L
+
 // Set the highest bit of each 32 bit chunk in a 64 bit word
-internal const val Uint64High32 = -0x7fffffff_80000000L
+@PublishedApi internal const val Uint64High32 = -0x7fffffff_80000000L
+
 // Set the lowest bit of each 32 bit chunk in a 64 bit word
-internal const val Uint64Low32 = 0x00000001_00000001L
+@PublishedApi internal const val Uint64Low32 = 0x00000001_00000001L
+
 // Encodes the first valid NaN in each of the 32 bit chunk of a 64 bit word
-internal const val DualFirstNaN = 0x7f800001_7f800001L
-
-// This function exists so we do *not* inline the throw. It keeps
-// the call site much smaller and since it's the slow path anyway,
-// we don't mind the extra function call
-internal fun throwIllegalStateException(message: String) {
-    throw IllegalStateException(message)
-}
-
-// Like Kotlin's require() but without the .toString() call
-@OptIn(ExperimentalContracts::class)
-internal inline fun checkPrecondition(value: Boolean, lazyMessage: () -> String) {
-    contract { returns() implies value }
-    if (!value) {
-        throwIllegalStateException(lazyMessage())
-    }
-}
+@PublishedApi internal const val DualFirstNaN = 0x7f800001_7f800001L
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 174280d..de47854 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
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package androidx.compose.ui.geometry
 
 import androidx.compose.runtime.Stable
@@ -44,7 +46,7 @@
 
     /** Whether this rectangle encloses a non-zero area. Negative areas are considered empty. */
     val isEmpty: Boolean
-        get() = left >= right || top >= bottom
+        get() = (left >= right) or (top >= bottom)
 
     /**
      * Modifies `this` to be the intersection of this and the rect formed by [left], [top], [right],
@@ -65,7 +67,9 @@
      * Rectangles include their top and left edges but exclude their bottom and right edges.
      */
     operator fun contains(offset: Offset): Boolean {
-        return offset.x >= left && offset.x < right && offset.y >= top && offset.y < bottom
+        val x = offset.x
+        val y = offset.y
+        return (x >= left) and (x < right) and (y >= top) and (y < bottom)
     }
 
     /** Sets new bounds to ([left], [top], [right], [bottom]) */
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index 0007a78..92ed64e 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -34,9 +34,9 @@
  * 1. As representing a point in Cartesian space a specified distance from a separately-maintained
  *    origin. For example, the top-left position of children in the [RenderBox] protocol is
  *    typically represented as an [Offset] from the top left of the parent box.
- * 2. As a vector that can be applied to coordinates. For example, when painting a [RenderObject],
- *    the parent is passed an [Offset] from the screen's origin which it can add to the offsets of
- *    its children to find the [Offset] from the screen's origin to each of the children.
+ * 2. As a vector that can be applied to coordinates. For example, when painting a widget, the
+ *    parent is passed an [Offset] from the screen's origin which it can add to the offsets of its
+ *    children to find the [Offset] from the screen's origin to each of the children.
  *
  * Because a particular [Offset] can be interpreted as one sense at one time then as the other sense
  * at a later time, the same class is used for both senses.
@@ -50,13 +50,15 @@
 @Suppress("NOTHING_TO_INLINE")
 @Immutable
 @kotlin.jvm.JvmInline
-value class Offset internal constructor(internal val packedValue: Long) {
+value class Offset
+@PublishedApi
+internal constructor(@PublishedApi internal val packedValue: Long) {
     @Stable
-    val x: Float
+    inline val x: Float
         get() = unpackFloat1(packedValue)
 
     @Stable
-    val y: Float
+    inline val y: Float
         get() = unpackFloat2(packedValue)
 
     @Stable inline operator fun component1(): Float = x
@@ -97,7 +99,7 @@
      * - True otherwise
      */
     @Stable
-    fun isValid(): Boolean {
+    inline fun isValid(): Boolean {
         // Take the unsigned packed floats and see if they are < InfinityBase + 1 (first NaN)
         val v = packedValue and DualUnsignedFloatMask
         return (v - DualFirstNaN) and Uint64High32 == Uint64High32
@@ -137,7 +139,7 @@
      * pointing in the reverse direction.
      */
     @Stable
-    operator fun unaryMinus(): Offset {
+    inline operator fun unaryMinus(): Offset {
         return Offset(packedValue xor DualFloatSignBit)
     }
 
@@ -249,7 +251,7 @@
 
 /** True if both x and y values of the [Offset] are finite. NaN values are not considered finite. */
 @Stable
-val Offset.isFinite: Boolean
+inline val Offset.isFinite: Boolean
     get() {
         // Mask out the sign bit and do an equality check in each 32-bit lane
         // against the "infinity base" mask (to check whether each packed float
@@ -260,12 +262,12 @@
 
 /** `false` when this is [Offset.Unspecified]. */
 @Stable
-val Offset.isSpecified: Boolean
+inline val Offset.isSpecified: Boolean
     get() = packedValue and DualUnsignedFloatMask != UnspecifiedPackedFloats
 
 /** `true` when this is [Offset.Unspecified]. */
 @Stable
-val Offset.isUnspecified: Boolean
+inline val Offset.isUnspecified: Boolean
     get() = packedValue and DualUnsignedFloatMask == UnspecifiedPackedFloats
 
 /**
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 bdc6c9b..b7a225a 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
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package androidx.compose.ui.geometry
 
 import androidx.compose.runtime.Immutable
@@ -44,24 +46,19 @@
 ) {
 
     companion object {
-
         /** A rectangle with left, top, right, and bottom edges all at zero. */
         @Stable val Zero: Rect = Rect(0.0f, 0.0f, 0.0f, 0.0f)
     }
 
     /** The distance between the left and right edges of this rectangle. */
     @Stable
-    val width: Float
-        get() {
-            return right - left
-        }
+    inline val width: Float
+        get() = right - left
 
     /** The distance between the top and bottom edges of this rectangle. */
     @Stable
-    val height: Float
-        get() {
-            return bottom - top
-        }
+    inline val height: Float
+        get() = bottom - top
 
     /** The distance between the upper-left corner and the lower-right corner of this rectangle. */
     @Stable
@@ -73,20 +70,24 @@
     @Stable
     val isInfinite: Boolean
         get() =
-            left >= Float.POSITIVE_INFINITY ||
-                top >= Float.POSITIVE_INFINITY ||
-                right >= Float.POSITIVE_INFINITY ||
-                bottom >= Float.POSITIVE_INFINITY
+            (left == Float.POSITIVE_INFINITY) or
+                (top == Float.POSITIVE_INFINITY) or
+                (right == Float.POSITIVE_INFINITY) or
+                (bottom == Float.POSITIVE_INFINITY)
 
     /** Whether all coordinates of this rectangle are finite. */
     @Stable
     val isFinite: Boolean
-        get() = left.isFinite() && top.isFinite() && right.isFinite() && bottom.isFinite()
+        get() =
+            ((left.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
+                ((top.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
+                ((right.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
+                ((bottom.toRawBits() and 0x7fffffff) < FloatInfinityBase)
 
     /** Whether this rectangle encloses a non-zero area. Negative areas are considered empty. */
     @Stable
     val isEmpty: Boolean
-        get() = left >= right || top >= bottom
+        get() = (left >= right) or (top >= bottom)
 
     /**
      * Returns a new rectangle translated by the given offset.
@@ -149,9 +150,10 @@
 
     /** Whether `other` has a nonzero area of overlap with this rectangle. */
     fun overlaps(other: Rect): Boolean {
-        if (right <= other.left || other.right <= left) return false
-        if (bottom <= other.top || other.bottom <= top) return false
-        return true
+        return (left < other.right) and
+            (other.left < right) and
+            (top < other.bottom) and
+            (other.top < bottom)
     }
 
     /** The lesser of the magnitudes of the [width] and the [height] of this rectangle. */
@@ -214,7 +216,9 @@
      * Rectangles include their top and left edges but exclude their bottom and right edges.
      */
     operator fun contains(offset: Offset): Boolean {
-        return offset.x >= left && offset.x < right && offset.y >= top && offset.y < bottom
+        val x = offset.x
+        val y = offset.y
+        return (x >= left) and (x < right) and (y >= top) and (y < bottom)
     }
 
     override fun toString() =
@@ -273,11 +277,10 @@
  * `AnimationController`.
  */
 @Stable
-fun lerp(start: Rect, stop: Rect, fraction: Float): Rect {
-    return Rect(
+fun lerp(start: Rect, stop: Rect, fraction: Float): Rect =
+    Rect(
         lerp(start.left, stop.left, fraction),
         lerp(start.top, stop.top, fraction),
         lerp(start.right, stop.right, fraction),
         lerp(start.bottom, stop.bottom, fraction)
     )
-}
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 c38b4353..e2b7e68 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
@@ -17,6 +17,7 @@
 package androidx.compose.ui.geometry
 
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.util.fastIsFinite
 import androidx.compose.ui.util.lerp
 import kotlin.math.absoluteValue
 import kotlin.math.max
@@ -339,7 +340,8 @@
 
 /** Whether all coordinates of this rounded rectangle are finite. */
 val RoundRect.isFinite
-    get() = left.isFinite() && top.isFinite() && right.isFinite() && bottom.isFinite()
+    get() =
+        left.fastIsFinite() && top.fastIsFinite() && right.fastIsFinite() && bottom.fastIsFinite()
 
 /** Whether this rounded rectangle is a simple rectangle with zero corner radii. */
 val RoundRect.isRect
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
index 72157d0..d47e8e4 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package androidx.compose.ui.geometry
 
 import androidx.compose.runtime.Immutable
@@ -28,7 +30,7 @@
 import kotlin.math.min
 
 /** Constructs a [Size] from the given width and height */
-@Stable fun Size(width: Float, height: Float) = Size(packFloats(width, height))
+@Stable inline fun Size(width: Float, height: Float) = Size(packFloats(width, height))
 
 /**
  * Holds a 2D floating-point size.
@@ -37,30 +39,18 @@
  */
 @Immutable
 @kotlin.jvm.JvmInline
-value class Size internal constructor(@PublishedApi internal val packedValue: Long) {
+value class Size @PublishedApi internal constructor(@PublishedApi internal val packedValue: Long) {
     @Stable
-    val width: Float
-        get() {
-            // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
-            if (packedValue == UnspecifiedPackedFloats) {
-                throwIllegalStateException("Size is unspecified")
-            }
-            return unpackFloat1(packedValue)
-        }
+    inline val width: Float
+        get() = unpackFloat1(packedValue)
 
     @Stable
-    val height: Float
-        get() {
-            // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
-            if (packedValue == UnspecifiedPackedFloats) {
-                throwIllegalStateException("Size is unspecified")
-            }
-            return unpackFloat2(packedValue)
-        }
+    inline val height: Float
+        get() = unpackFloat2(packedValue)
 
-    @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component1(): Float = width
+    @Stable inline operator fun component1(): Float = width
 
-    @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component2(): Float = height
+    @Stable inline operator fun component2(): Float = height
 
     /** Returns a copy of this Size instance optionally overriding the width or height parameter */
     fun copy(width: Float = unpackFloat1(packedValue), height: Float = unpackFloat2(packedValue)) =
@@ -86,20 +76,19 @@
      */
     @Stable
     fun isEmpty(): Boolean {
-        if (packedValue == UnspecifiedPackedFloats) {
-            throwIllegalStateException("Size is unspecified")
-        }
         // Mask the sign bits, shift them to the right and replicate them by multiplying by -1.
         // This will give us a mask of 0xffff_ffff for negative packed floats, and 0x0000_0000
         // for positive packed floats. We invert the mask and do an and operation with the
         // original value to set any negative float to 0.0f.
         val v = packedValue and ((packedValue and DualFloatSignBit ushr 31) * -0x1).inv()
-        // At this point any negative float is set to 0, so the sign bit is  always 0.
+        // At this point any negative float is set to 0, so the sign bit is always 0.
         // We take the 2 packed floats and "and" them together: if any of the two floats
         // is 0.0f (either because the original value is 0.0f or because it was negative and
         // we turned it into 0.0f with the line above), the result of the and operation will
         // be 0 and we know our Size is empty.
-        return ((v ushr 32) and (v and 0xffffffffL)) == 0L
+        val w = (v ushr 32) and (v and 0xffffffffL)
+        // We treat Size.Unspecified as being empty
+        return (w == 0L) or (packedValue == UnspecifiedPackedFloats)
     }
 
     /**
@@ -109,14 +98,8 @@
      * multiplied by the scalar right-hand-side operand (a [Float]).
      */
     @Stable
-    operator fun times(operand: Float): Size {
-        if (packedValue == UnspecifiedPackedFloats) {
-            throwIllegalStateException("Size is unspecified")
-        }
-        return Size(
-            packFloats(unpackFloat1(packedValue) * operand, unpackFloat2(packedValue) * operand)
-        )
-    }
+    operator fun times(operand: Float): Size =
+        Size(packFloats(unpackFloat1(packedValue) * operand, unpackFloat2(packedValue) * operand))
 
     /**
      * Division operator.
@@ -125,34 +108,18 @@
      * divided by the scalar right-hand-side operand (a [Float]).
      */
     @Stable
-    operator fun div(operand: Float): Size {
-        if (packedValue == UnspecifiedPackedFloats) {
-            throwIllegalStateException("Size is unspecified")
-        }
-        return Size(
-            packFloats(unpackFloat1(packedValue) / operand, unpackFloat2(packedValue) / operand)
-        )
-    }
+    operator fun div(operand: Float): Size =
+        Size(packFloats(unpackFloat1(packedValue) / operand, unpackFloat2(packedValue) / operand))
 
     /** The lesser of the magnitudes of the [width] and the [height]. */
     @Stable
     val minDimension: Float
-        get() {
-            if (packedValue == UnspecifiedPackedFloats) {
-                throwIllegalStateException("Size is unspecified")
-            }
-            return min(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
-        }
+        get() = min(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
 
     /** The greater of the magnitudes of the [width] and the [height]. */
     @Stable
     val maxDimension: Float
-        get() {
-            if (packedValue == UnspecifiedPackedFloats) {
-                throwIllegalStateException("Size is unspecified")
-            }
-            return max(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
-        }
+        get() = max(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
 
     override fun toString() =
         if (isSpecified) {
@@ -194,45 +161,27 @@
  * `AnimationController`.
  */
 @Stable
-fun lerp(start: Size, stop: Size, fraction: Float): Size {
-    if (
-        start.packedValue == UnspecifiedPackedFloats || stop.packedValue == UnspecifiedPackedFloats
-    ) {
-        throwIllegalStateException("Offset is unspecified")
-    }
-    return Size(
+fun lerp(start: Size, stop: Size, fraction: Float): Size =
+    Size(
         packFloats(
             lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
             lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
         )
     )
-}
 
 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
-@Suppress("NOTHING_TO_INLINE")
-@Stable
-inline operator fun Int.times(size: Size) = size * this.toFloat()
+@Stable inline operator fun Int.times(size: Size) = size * this.toFloat()
 
 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
-@Suppress("NOTHING_TO_INLINE")
-@Stable
-inline operator fun Double.times(size: Size) = size * this.toFloat()
+@Stable inline operator fun Double.times(size: Size) = size * this.toFloat()
 
 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
-@Suppress("NOTHING_TO_INLINE") @Stable inline operator fun Float.times(size: Size) = size * this
+@Stable inline operator fun Float.times(size: Size) = size * this
 
 /** Convert a [Size] to a [Rect]. */
-@Stable
-fun Size.toRect(): Rect {
-    return Rect(Offset.Zero, this)
-}
+@Stable fun Size.toRect(): Rect = Rect(Offset.Zero, this)
 
 /** Returns the [Offset] of the center of the rect from the point of [0, 0] with this [Size]. */
 @Stable
 val Size.center: Offset
-    get() {
-        if (packedValue == UnspecifiedPackedFloats) {
-            throwIllegalStateException("Size is unspecified")
-        }
-        return Offset(unpackFloat1(packedValue) / 2f, unpackFloat2(packedValue) / 2f)
-    }
+    get() = Offset(unpackFloat1(packedValue) / 2f, unpackFloat2(packedValue) / 2f)
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
index a1e2b33..f3a63c7 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.geometry.isFinite
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.util.fastIsFinite
 import kotlin.math.abs
 
 @Immutable
@@ -505,7 +506,12 @@
 ) : ShaderBrush() {
 
     override val intrinsicSize: Size
-        get() = if (radius.isFinite()) Size(radius * 2, radius * 2) else Size.Unspecified
+        get() =
+            if (radius.fastIsFinite()) {
+                Size(radius * 2, radius * 2)
+            } else {
+                Size.Unspecified
+            }
 
     override fun createShader(size: Size): Shader {
         val centerX: Float
@@ -552,7 +558,7 @@
 
     override fun toString(): String {
         val centerValue = if (center.isSpecified) "center=$center, " else ""
-        val radiusValue = if (radius.isFinite()) "radius=$radius, " else ""
+        val radiusValue = if (radius.fastIsFinite()) "radius=$radius, " else ""
         return "RadialGradient(" +
             "colors=$colors, " +
             "stops=$stops, " +
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt
index 49226ac..00b7dde 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt
@@ -20,10 +20,11 @@
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.util.fastIsFinite
+import androidx.compose.ui.util.fastMaxOf
+import androidx.compose.ui.util.fastMinOf
 import androidx.compose.ui.util.normalizedAngleCos
 import androidx.compose.ui.util.normalizedAngleSin
-import kotlin.math.max
-import kotlin.math.min
 
 // NOTE: This class contains a number of tests like this:
 //
@@ -54,43 +55,133 @@
         // See top-level comment
         if (values.size < 16) return point
 
+        val v00 = this[0, 0]
+        val v01 = this[0, 1]
+        val v03 = this[0, 3]
+        val v10 = this[1, 0]
+        val v11 = this[1, 1]
+        val v13 = this[1, 3]
+        val v30 = this[3, 0]
+        val v31 = this[3, 1]
+        val v33 = this[3, 3]
+
         val x = point.x
         val y = point.y
-        val z = this[0, 3] * x + this[1, 3] * y + this[3, 3]
+        val z = v03 * x + v13 * y + v33
         val inverseZ = 1 / z
-        val pZ = if (inverseZ.isFinite()) inverseZ else 0f
+        val pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
 
-        return Offset(
-            x = pZ * (this[0, 0] * x + this[1, 0] * y + this[3, 0]),
-            y = pZ * (this[0, 1] * x + this[1, 1] * y + this[3, 1])
-        )
+        return Offset(x = pZ * (v00 * x + v10 * y + v30), y = pZ * (v01 * x + v11 * y + v31))
     }
 
     /** Does a 3D transform on [rect] and returns its bounds after the transform. */
     fun map(rect: Rect): Rect {
-        val p0 = map(Offset(rect.left, rect.top))
-        val p1 = map(Offset(rect.left, rect.bottom))
-        val p3 = map(Offset(rect.right, rect.top))
-        val p4 = map(Offset(rect.right, rect.bottom))
+        // See top-level comment
+        if (values.size < 16) return rect
 
-        val left = min(min(p0.x, p1.x), min(p3.x, p4.x))
-        val top = min(min(p0.y, p1.y), min(p3.y, p4.y))
-        val right = max(max(p0.x, p1.x), max(p3.x, p4.x))
-        val bottom = max(max(p0.y, p1.y), max(p3.y, p4.y))
-        return Rect(left, top, right, bottom)
+        val v00 = this[0, 0]
+        val v01 = this[0, 1]
+        val v03 = this[0, 3]
+        val v10 = this[1, 0]
+        val v11 = this[1, 1]
+        val v13 = this[1, 3]
+        val v30 = this[3, 0]
+        val v31 = this[3, 1]
+        val v33 = this[3, 3]
+
+        val l = rect.left
+        val t = rect.top
+        val r = rect.right
+        val b = rect.bottom
+
+        var x = l
+        var y = t
+        var inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        var pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x0 = pZ * (v00 * x + v10 * y + v30)
+        val y0 = pZ * (v01 * x + v11 * y + v31)
+
+        x = l
+        y = b
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x1 = pZ * (v00 * x + v10 * y + v30)
+        val y1 = pZ * (v01 * x + v11 * y + v31)
+
+        x = r
+        y = t
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x2 = pZ * (v00 * x + v10 * y + v30)
+        val y2 = pZ * (v01 * x + v11 * y + v31)
+
+        x = r
+        y = b
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x3 = pZ * (v00 * x + v10 * y + v30)
+        val y3 = pZ * (v01 * x + v11 * y + v31)
+
+        return Rect(
+            fastMinOf(x0, x1, x2, x3),
+            fastMinOf(y0, y1, y2, y3),
+            fastMaxOf(x0, x1, x2, x3),
+            fastMaxOf(y0, y1, y2, y3)
+        )
     }
 
     /** Does a 3D transform on [rect], transforming [rect] with the results. */
     fun map(rect: MutableRect) {
-        val p0 = map(Offset(rect.left, rect.top))
-        val p1 = map(Offset(rect.left, rect.bottom))
-        val p3 = map(Offset(rect.right, rect.top))
-        val p4 = map(Offset(rect.right, rect.bottom))
+        // See top-level comment
+        if (values.size < 16) return
 
-        rect.left = min(min(p0.x, p1.x), min(p3.x, p4.x))
-        rect.top = min(min(p0.y, p1.y), min(p3.y, p4.y))
-        rect.right = max(max(p0.x, p1.x), max(p3.x, p4.x))
-        rect.bottom = max(max(p0.y, p1.y), max(p3.y, p4.y))
+        val v00 = this[0, 0]
+        val v01 = this[0, 1]
+        val v03 = this[0, 3]
+        val v10 = this[1, 0]
+        val v11 = this[1, 1]
+        val v13 = this[1, 3]
+        val v30 = this[3, 0]
+        val v31 = this[3, 1]
+        val v33 = this[3, 3]
+
+        val l = rect.left
+        val t = rect.top
+        val r = rect.right
+        val b = rect.bottom
+
+        var x = l
+        var y = t
+        var inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        var pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x0 = pZ * (v00 * x + v10 * y + v30)
+        val y0 = pZ * (v01 * x + v11 * y + v31)
+
+        x = l
+        y = b
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x1 = pZ * (v00 * x + v10 * y + v30)
+        val y1 = pZ * (v01 * x + v11 * y + v31)
+
+        x = r
+        y = t
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x2 = pZ * (v00 * x + v10 * y + v30)
+        val y2 = pZ * (v01 * x + v11 * y + v31)
+
+        x = r
+        y = b
+        inverseZ = 1.0f / (v03 * x + v13 * y + v33)
+        pZ = if (inverseZ.fastIsFinite()) inverseZ else 0f
+        val x3 = pZ * (v00 * x + v10 * y + v30)
+        val y3 = pZ * (v01 * x + v11 * y + v31)
+
+        rect.left = fastMinOf(x0, x1, x2, x3)
+        rect.top = fastMinOf(y0, y1, y2, y3)
+        rect.right = fastMaxOf(x0, x1, x2, x3)
+        rect.bottom = fastMaxOf(y0, y1, y2, y3)
     }
 
     /** Multiply this matrix by [m] and assign the result to this matrix. */
diff --git a/compose/ui/ui-test-junit4/api/current.txt b/compose/ui/ui-test-junit4/api/current.txt
index edfb551..b73b73b 100644
--- a/compose/ui/ui-test-junit4/api/current.txt
+++ b/compose/ui/ui-test-junit4/api/current.txt
@@ -7,6 +7,7 @@
     method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void cancelAndRecreateRecomposer();
+    method public com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? getAccessibilityValidator();
     method public A getActivity();
     method public R getActivityRule();
     method public androidx.compose.ui.unit.Density getDensity();
@@ -16,6 +17,7 @@
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
     method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setAccessibilityValidator(com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator?);
     method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
     method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public void waitForIdle();
@@ -24,6 +26,7 @@
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis);
+    property public final com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? accessibilityValidator;
     property public final A activity;
     property public final R activityRule;
     property public androidx.compose.ui.unit.Density density;
@@ -52,6 +55,8 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ComposeTestRule extends org.junit.rules.TestRule androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public default void disableAccessibilityChecks();
+    method public default void enableAccessibilityChecks();
     method public androidx.compose.ui.unit.Density getDensity();
     method public androidx.compose.ui.test.MainTestClock getMainClock();
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.txt b/compose/ui/ui-test-junit4/api/restricted_current.txt
index edfb551..b73b73b 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_current.txt
@@ -7,6 +7,7 @@
     method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void cancelAndRecreateRecomposer();
+    method public com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? getAccessibilityValidator();
     method public A getActivity();
     method public R getActivityRule();
     method public androidx.compose.ui.unit.Density getDensity();
@@ -16,6 +17,7 @@
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
     method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setAccessibilityValidator(com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator?);
     method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
     method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public void waitForIdle();
@@ -24,6 +26,7 @@
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis);
+    property public final com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? accessibilityValidator;
     property public final A activity;
     property public final R activityRule;
     property public androidx.compose.ui.unit.Density density;
@@ -52,6 +55,8 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ComposeTestRule extends org.junit.rules.TestRule androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public default void disableAccessibilityChecks();
+    method public default void enableAccessibilityChecks();
     method public androidx.compose.ui.unit.Density getDensity();
     method public androidx.compose.ui.test.MainTestClock getMainClock();
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
index 4d70df8..45f1c50 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
@@ -31,8 +31,11 @@
 import androidx.compose.ui.test.waitUntilNodeCount
 import androidx.compose.ui.unit.Density
 import androidx.test.ext.junit.rules.ActivityScenarioRule
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestDispatcher
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
@@ -82,6 +85,11 @@
  * with your own launcher.
  *
  * If your test doesn't require a specific Activity, use [createComposeRule] instead.
+ *
+ * @param effectContext The [CoroutineContext] used to run the composition. The context for
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 inline fun <reified A : ComponentActivity> createAndroidComposeRule(
@@ -131,6 +139,12 @@
  * with your own launcher.
  *
  * If your test doesn't require a specific Activity, use [createComposeRule] instead.
+ *
+ * @param activityClass The activity type to use in the activity scenario
+ * @param effectContext The [CoroutineContext] used to run the composition. The context for
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 fun <A : ComponentActivity> createAndroidComposeRule(
@@ -179,7 +193,9 @@
  * after one or more dependencies have been injected.
  *
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 fun createEmptyComposeRule(
@@ -248,7 +264,9 @@
      *
      * @param activityRule Test rule to use to launch the Activity.
      * @param effectContext The [CoroutineContext] used to run the composition. The context for
-     *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+     *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+     *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+     *   used for composition and the [MainTestClock].
      * @param activityProvider Function to retrieve the Activity from the given [activityRule].
      */
     @ExperimentalTestApi
@@ -299,6 +317,23 @@
     override val mainClock: MainTestClock
         get() = composeTest.mainClock
 
+    /**
+     * The [AccessibilityValidator] that will be used to run Android accessibility checks before
+     * every action that is expected to change the UI.
+     *
+     * If no validator is set (`null`), no checks will be performed. You can either supply your own
+     * validator directly, or have one configured for you with [enableAccessibilityChecks].
+     *
+     * The default value is `null`.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withAndroidComposeTestRule_sample
+     */
+    var accessibilityValidator: AccessibilityValidator?
+        get() = composeTest.accessibilityValidator
+        set(value) {
+            composeTest.accessibilityValidator = value
+        }
+
     override fun <T> runOnUiThread(action: () -> T): T = composeTest.runOnUiThread(action)
 
     override fun <T> runOnIdle(action: () -> T): T = composeTest.runOnIdle(action)
@@ -340,6 +375,28 @@
     override fun unregisterIdlingResource(idlingResource: IdlingResource) =
         composeTest.unregisterIdlingResource(idlingResource)
 
+    /**
+     * Enables accessibility checks that will be run before every action that is expected to change
+     * the UI.
+     *
+     * This will create and set an [accessibilityValidator] if there isn't one yet, or will do
+     * nothing if an `accessibilityValidator` is already set.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withComposeTestRule_sample
+     * @see disableAccessibilityChecks
+     */
+    override fun enableAccessibilityChecks() = composeTest.enableAccessibilityChecks()
+
+    /**
+     * Disables accessibility checks.
+     *
+     * This will set the [accessibilityValidator] back to `null`.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withAndroidComposeTestRule_sample
+     * @see enableAccessibilityChecks
+     */
+    override fun disableAccessibilityChecks() = composeTest.disableAccessibilityChecks()
+
     override fun onNode(
         matcher: SemanticsMatcher,
         useUnmergedTree: Boolean
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
index d0253f0..7589a1d 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
+++ b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
@@ -25,6 +25,8 @@
 import androidx.compose.ui.unit.Density
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestDispatcher
 import org.junit.rules.TestRule
 
 /**
@@ -225,6 +227,30 @@
 
     /** Unregisters an [IdlingResource] from this test. */
     fun unregisterIdlingResource(idlingResource: IdlingResource)
+
+    /**
+     * Enables accessibility checks that will be run before every action that is expected to change
+     * the UI.
+     *
+     * Accessibility checks are platform dependent, refer to the documentation of the platform
+     * specific variant of [ComposeTestRule] to see if it is supported and how you can configure it.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withComposeTestRule_sample
+     * @see disableAccessibilityChecks
+     */
+    fun enableAccessibilityChecks() {
+        throw NotImplementedError("Accessibility Checks are not implemented on this platform")
+    }
+
+    /**
+     * Disables accessibility checks.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withAndroidComposeTestRule_sample
+     * @see enableAccessibilityChecks
+     */
+    fun disableAccessibilityChecks() {
+        throw NotImplementedError("Accessibility Checks are not implemented on this platform")
+    }
 }
 
 /**
@@ -281,7 +307,9 @@
  * launched, see [createAndroidComposeRule].
  *
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 expect fun createComposeRule(
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index c9bf080..21ab6f74 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -21,6 +21,11 @@
     method @Deprecated public static <T extends kotlin.Function<? extends java.lang.Boolean>> void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<T>> key, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> invocation);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performTouchInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.TouchInjectionScope,kotlin.Unit> block);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction requestFocus(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method public static androidx.compose.ui.test.SemanticsNodeInteractionCollection tryPerformAccessibilityChecks(androidx.compose.ui.test.SemanticsNodeInteractionCollection);
+  }
+
+  public final class AndroidActions {
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction tryPerformAccessibilityChecks(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
 
   @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
@@ -95,15 +100,20 @@
 
   @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void disableAccessibilityChecks();
+    method public void enableAccessibilityChecks();
+    method public com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? getAccessibilityValidator();
     method public androidx.compose.ui.unit.Density getDensity();
     method public androidx.compose.ui.test.MainTestClock getMainClock();
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
     method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setAccessibilityValidator(com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator?);
     method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
     method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public void waitForIdle();
     method public void waitUntil(optional String? conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+    property public abstract com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? accessibilityValidator;
     property public abstract androidx.compose.ui.unit.Density density;
     property public abstract androidx.compose.ui.test.MainTestClock mainClock;
   }
@@ -576,7 +586,7 @@
 package androidx.compose.ui.test.internal {
 
   @SuppressCompatibility @androidx.compose.ui.test.InternalTestApi public abstract class DelayPropagatingContinuationInterceptorWrapper extends kotlin.coroutines.AbstractCoroutineContextElement implements kotlin.coroutines.ContinuationInterceptor kotlinx.coroutines.Delay {
-    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor? wrappedInterceptor);
+    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor wrappedInterceptor);
   }
 
 }
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index fa6e38c..2f5bc6c 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -21,6 +21,11 @@
     method @Deprecated public static <T extends kotlin.Function<? extends java.lang.Boolean>> void performSemanticsAction(androidx.compose.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<T>> key, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> invocation);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performTouchInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.TouchInjectionScope,kotlin.Unit> block);
     method public static androidx.compose.ui.test.SemanticsNodeInteraction requestFocus(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method public static androidx.compose.ui.test.SemanticsNodeInteractionCollection tryPerformAccessibilityChecks(androidx.compose.ui.test.SemanticsNodeInteractionCollection);
+  }
+
+  public final class AndroidActions {
+    method public static androidx.compose.ui.test.SemanticsNodeInteraction tryPerformAccessibilityChecks(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
 
   @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
@@ -95,15 +100,20 @@
 
   @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void disableAccessibilityChecks();
+    method public void enableAccessibilityChecks();
+    method public com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? getAccessibilityValidator();
     method public androidx.compose.ui.unit.Density getDensity();
     method public androidx.compose.ui.test.MainTestClock getMainClock();
     method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
     method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setAccessibilityValidator(com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator?);
     method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
     method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
     method public void waitForIdle();
     method public void waitUntil(optional String? conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+    property public abstract com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator? accessibilityValidator;
     property public abstract androidx.compose.ui.unit.Density density;
     property public abstract androidx.compose.ui.test.MainTestClock mainClock;
   }
@@ -577,7 +587,7 @@
 package androidx.compose.ui.test.internal {
 
   @SuppressCompatibility @androidx.compose.ui.test.InternalTestApi public abstract class DelayPropagatingContinuationInterceptorWrapper extends kotlin.coroutines.AbstractCoroutineContextElement implements kotlin.coroutines.ContinuationInterceptor kotlinx.coroutines.Delay {
-    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor? wrappedInterceptor);
+    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor wrappedInterceptor);
   }
 
 }
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 4e13f19..653f362 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -67,6 +67,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api(project(":compose:ui:ui-graphics"))
+                api("com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:4.1.1")
                 implementation("androidx.activity:activity-compose:1.3.0")
                 implementation("androidx.annotation:annotation:1.8.1")
                 implementation("androidx.core:core-ktx:1.12.0")
diff --git a/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/AccessibilityChecksSamples.kt b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/AccessibilityChecksSamples.kt
new file mode 100644
index 0000000..13a55b2
--- /dev/null
+++ b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/AccessibilityChecksSamples.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test.samples
+
+import androidx.activity.ComponentActivity
+import androidx.annotation.Sampled
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
+import androidx.compose.ui.test.runAndroidComposeUiTest
+import androidx.compose.ui.test.runComposeUiTest
+import androidx.compose.ui.test.tryPerformAccessibilityChecks
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
+import org.junit.Test
+
+/**
+ * Sample that shows how to enable accessibility checks from common code, when using a
+ * ComposeTestRule.
+ */
+@Sampled
+fun accessibilityChecks_withComposeTestRule_sample() {
+    // Enable accessibility checks with default configuration:
+    composeTestRule.enableAccessibilityChecks()
+
+    // Accessibility checks are run automatically when performing an action:
+    composeTestRule.onNodeWithText("Submit").performClick()
+
+    // You can also manually run accessibility checks:
+    composeTestRule.onRoot().tryPerformAccessibilityChecks()
+
+    // When disabling accessibility checks..
+    composeTestRule.disableAccessibilityChecks()
+
+    // .. they no longer run when performing an action:
+    composeTestRule.onNodeWithTag("list").performScrollToIndex(15)
+}
+
+/**
+ * Sample that shows how to enable accessibility checks from common code, when using
+ * runComposeUiTest {}.
+ */
+@Sampled
+fun accessibilityChecks_withComposeUiTest_sample() {
+    @Test
+    @OptIn(ExperimentalTestApi::class)
+    fun testWithAccessibilityChecks() = runComposeUiTest {
+        // Enable accessibility checks with default configuration:
+        enableAccessibilityChecks()
+
+        // When enabled, accessibility checks run automatically when performing an action:
+        onNodeWithText("Submit").performClick()
+
+        // You can also manually run accessibility checks:
+        onRoot().tryPerformAccessibilityChecks()
+
+        // When disabling accessibility checks..
+        disableAccessibilityChecks()
+
+        // .. they no longer run when performing an action:
+        onNodeWithTag("list").performScrollToIndex(15)
+    }
+}
+
+/**
+ * Sample that shows how to enable accessibility checks from android code, when using a
+ * AndroidComposeTestRule<A : ComponentActivity>.
+ */
+@Sampled
+fun accessibilityChecks_withAndroidComposeTestRule_sample() {
+    // Enable accessibility checks with your own AccessibilityValidator:
+    androidComposeTestRule.accessibilityValidator = AccessibilityValidator()
+    // By setting a non-null AccessibilityValidator, accessibility checks are enabled
+
+    // Configure the AccessibilityValidator:
+    androidComposeTestRule.accessibilityValidator!!.setThrowExceptionFor(
+        AccessibilityCheckResultType.ERROR
+    )
+}
+
+/**
+ * Sample that shows how to enable accessibility checks from android code, when using
+ * runAndroidComposeUiTest<A : ComponentActivity> {}.
+ */
+@Sampled
+fun accessibilityChecks_withAndroidComposeUiTest_sample() {
+    @Test
+    @OptIn(ExperimentalTestApi::class)
+    fun testWithAccessibilityChecks() =
+        runAndroidComposeUiTest<ComponentActivity> {
+            // Enable accessibility checks with your own AccessibilityValidator:
+            accessibilityValidator = AccessibilityValidator()
+            // By setting a non-null AccessibilityValidator, accessibility checks are enabled
+
+            // Configure the AccessibilityValidator:
+            @OptIn(ExperimentalTestApi::class)
+            accessibilityValidator!!.setThrowExceptionFor(AccessibilityCheckResultType.ERROR)
+        }
+}
diff --git a/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/Common.kt b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/Common.kt
index 397095f..f3d84dd 100644
--- a/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/Common.kt
+++ b/compose/ui/ui-test/samples/src/main/java/androidx/compose/ui/test/samples/Common.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.ui.test.samples
 
+import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.junit4.createComposeRule
 
 internal val composeTestRule = createComposeRule()
+internal val androidComposeTestRule = createAndroidComposeRule<ComponentActivity>()
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AccessibilityChecksTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AccessibilityChecksTest.kt
new file mode 100644
index 0000000..e1b9966
--- /dev/null
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AccessibilityChecksTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test
+
+import android.os.Build
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.expectError
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.test.filters.SdkSuppress
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@OptIn(ExperimentalTestApi::class)
+class AccessibilityChecksTest {
+
+    @Test
+    fun performAccessibilityChecks_findsNoErrors() = runComposeUiTest {
+        setContent { BoxWithoutProblems() }
+        enableAccessibilityChecks()
+
+        // There are no errors, this should not throw
+        onRoot().tryPerformAccessibilityChecks()
+    }
+
+    @Test
+    fun performAccessibilityChecks_findsOneError() = runComposeUiTest {
+        setContent { BoxWithMissingContentDescription() }
+        enableAccessibilityChecks()
+
+        expectError<AccessibilityViewCheckException>(
+            expectedMessage = "There was 1 accessibility result:.*"
+        ) {
+            // There is an error, this should throw
+            onRoot().tryPerformAccessibilityChecks()
+        }
+    }
+
+    @Test
+    fun performAccessibilityChecks_usesCustomValidator() =
+        runAndroidComposeUiTest<ComponentActivity> {
+            setContent { BoxWithoutProblems() }
+            var listenerInvocations = 0
+            // addCheckListener resolves to the overload that takes an AccessibilityCheckListener
+            accessibilityValidator =
+                AccessibilityValidator().addCheckListener { _, _ -> listenerInvocations++ }
+
+            // There are no errors, this should not throw
+            onRoot().tryPerformAccessibilityChecks()
+            // But our validator must have run checks once
+            assertThat(listenerInvocations).isEqualTo(1)
+        }
+
+    // Dialogs live in a sibling root view. Test if we catch problems in dialogs
+    @Test
+    fun performAccessibilityChecks_checksDialogs() = runComposeUiTest {
+        setContent {
+            BoxWithoutProblems()
+            Dialog({}) { BoxWithMissingContentDescription() }
+        }
+
+        enableAccessibilityChecks()
+        expectError<AccessibilityViewCheckException>(
+            expectedMessage = "There was 1 accessibility result:.*"
+        ) {
+            // There are no errors in the main screen, but there is one in the dialog, so this
+            // should throw
+            onRoot().tryPerformAccessibilityChecks()
+        }
+    }
+
+    // Checks that tryPerformAccessibilityChecks does not throw if the validator is thus configured
+    @Test
+    fun performAccessibilityChecks_allowsErrorCollection() =
+        runAndroidComposeUiTest<ComponentActivity> {
+            setContent { BoxWithMissingContentDescription() }
+            accessibilityValidator = AccessibilityValidator().setThrowExceptionFor(null)
+
+            // Despite the a11y error, this should not throw
+            onRoot().tryPerformAccessibilityChecks()
+        }
+
+    @Composable
+    private fun BoxWithoutProblems() {
+        Box(Modifier.size(20.dp))
+    }
+
+    @Composable
+    private fun BoxWithMissingContentDescription() {
+        Box(
+            Modifier.size(20.dp).semantics {
+                // The SemanticsModifier will make this node importantForAccessibility
+                // Having no content description is now a violation
+                this.contentDescription = ""
+            }
+        )
+    }
+}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/CustomEffectContextTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/CustomEffectContextTest.kt
index b4ba8f20..d7ddbb0 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/CustomEffectContextTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/CustomEffectContextTest.kt
@@ -24,10 +24,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -136,6 +140,21 @@
         }
     }
 
+    @Test
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun scheduler_usedWhenPresent() {
+        val scheduler = TestCoroutineScheduler()
+        val startTime = scheduler.currentTime
+
+        // We don't need any content, we only need to trigger the scheduler
+        runComposeUiTest(scheduler) {
+            setContent { rememberCoroutineScope().launch { withFrameNanos {} } }
+        }
+
+        // Only if it is used will the scheduler's time be changed
+        assertThat(scheduler.currentTime).isNotEqualTo(startTime)
+    }
+
     private class TestCoroutineContextElement : CoroutineContext.Element {
         override val key: CoroutineContext.Key<*>
             get() = Key
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/Actions.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/Actions.android.kt
index acfbe9f..8861924c 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/Actions.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/Actions.android.kt
@@ -13,9 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:JvmName("AndroidActions")
 
 package androidx.compose.ui.test
 
+import androidx.compose.ui.platform.ViewRootForTest
+
 internal actual fun SemanticsNodeInteraction.performClickImpl(): SemanticsNodeInteraction {
     return performTouchInput { click() }
 }
+
+@Suppress("DocumentExceptions") // Documented in expect fun
+actual fun SemanticsNodeInteraction.tryPerformAccessibilityChecks(): SemanticsNodeInteraction {
+    testContext.platform.accessibilityValidator?.let { av ->
+        testContext.testOwner
+            .getRoots(true)
+            .map {
+                // We're on Android, so we're guaranteed a ViewRootForTest
+                (it as ViewRootForTest).view.rootView
+            }
+            .distinct()
+            .run {
+                // Synchronization needs to happen off the UI thread, so only switch to the UI
+                // thread after the call to getRoots, but before the call to forEach so we don't
+                // have to switch thread for each root view.
+                testContext.testOwner.runOnUiThread { forEach { av.check(it) } }
+            }
+    }
+    return this
+}
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
index 3f308e8..23ba0f4 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
 import kotlin.coroutines.ContinuationInterceptor
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
@@ -39,6 +40,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -59,7 +61,9 @@
  * @param A The Activity type to be launched, which typically (but not necessarily) hosts the
  *   Compose content
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  * @param block The test function.
  */
 @ExperimentalTestApi
@@ -80,7 +84,9 @@
  *   Compose content
  * @param activityClass The [Class] of the Activity type to be launched, corresponding to [A].
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  * @param block The test function.
  */
 @ExperimentalTestApi
@@ -144,8 +150,8 @@
 }
 
 /**
- * Variant of [ComposeUiTest] for when you want to have access to the current [activity] of type
- * [A]. The activity might not always be available, for example if the test navigates to another
+ * Variant of [ComposeUiTest] for when you want to have access the current [activity] of type [A].
+ * The activity might not always be available, for example if the test navigates to another
  * activity. In such cases, [activity] will return `null`.
  *
  * An instance of [AndroidComposeUiTest] can be obtained by calling [runAndroidComposeUiTest], the
@@ -195,7 +201,9 @@
  * @param A The Activity type to be interacted with, which typically (but not necessarily) is the
  *   activity that was launched and hosts the Compose content.
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 inline fun <A : ComponentActivity> AndroidComposeUiTestEnvironment(
@@ -213,10 +221,17 @@
  * some of the properties and methods on [test] will only work during the call to [runTest], as they
  * require that the environment has been set up.
  *
+ * If the [effectContext] contains a [TestDispatcher], that dispatcher will be used to run
+ * composition on and its [TestCoroutineScheduler] will be used to construct the [MainTestClock]. If
+ * the `effectContext` does not contain a `TestDispatcher`, an [UnconfinedTestDispatcher] will be
+ * created, using the `TestCoroutineScheduler` from the `effectContext` if present.
+ *
  * @param A The Activity type to be interacted with, which typically (but not necessarily) is the
  *   activity that was launched and hosts the Compose content.
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  */
 @ExperimentalTestApi
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -234,7 +249,11 @@
     private lateinit var recomposer: Recomposer
     // We can only accept a TestDispatcher here because we need to access its scheduler.
     private val testCoroutineDispatcher =
-        effectContext[ContinuationInterceptor] as? TestDispatcher ?: UnconfinedTestDispatcher()
+        // Use the TestDispatcher if it is provided in the effectContext
+        effectContext[ContinuationInterceptor] as? TestDispatcher
+            ?:
+            // Otherwise, use the TestCoroutineScheduler if it is provided
+            UnconfinedTestDispatcher(effectContext[TestCoroutineScheduler])
     private val testCoroutineScope = TestScope(testCoroutineDispatcher)
     private lateinit var recomposerCoroutineScope: CoroutineScope
     private val coroutineExceptionHandler = UncaughtExceptionHandler()
@@ -437,6 +456,12 @@
         override val mainClock: MainTestClock
             get() = mainClockImpl
 
+        override var accessibilityValidator: AccessibilityValidator?
+            get() = testContext.platform.accessibilityValidator
+            set(value) {
+                testContext.platform.accessibilityValidator = value
+            }
+
         override fun <T> runOnUiThread(action: () -> T): T {
             return testOwner.runOnUiThread(action)
         }
@@ -494,6 +519,14 @@
             idlingResourceRegistry.unregisterIdlingResource(idlingResource)
         }
 
+        override fun enableAccessibilityChecks() {
+            accessibilityValidator = AccessibilityValidator().setRunChecksFromRootView(true)
+        }
+
+        override fun disableAccessibilityChecks() {
+            accessibilityValidator = null
+        }
+
         override fun onNode(
             matcher: SemanticsMatcher,
             useUnmergedTree: Boolean
@@ -595,6 +628,19 @@
     actual val density: Density
     actual val mainClock: MainTestClock
 
+    /**
+     * The [AccessibilityValidator] that will be used to run Android accessibility checks before
+     * every action that is expected to change the UI.
+     *
+     * If no validator is set (`null`), no checks will be performed. You can either supply your own
+     * validator directly, or have one configured for you with [enableAccessibilityChecks].
+     *
+     * The default value is `null`.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withAndroidComposeUiTest_sample
+     */
+    var accessibilityValidator: AccessibilityValidator?
+
     actual fun <T> runOnUiThread(action: () -> T): T
 
     actual fun <T> runOnIdle(action: () -> T): T
@@ -614,4 +660,8 @@
     actual fun unregisterIdlingResource(idlingResource: IdlingResource)
 
     actual fun setContent(composable: @Composable () -> Unit)
+
+    actual fun enableAccessibilityChecks()
+
+    actual fun disableAccessibilityChecks()
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalCameraInfo.java b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/TestContext.android.kt
similarity index 61%
rename from camera/camera-core/src/main/java/androidx/camera/core/ExperimentalCameraInfo.java
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/TestContext.android.kt
index 40a0707..f7747d7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalCameraInfo.java
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/TestContext.android.kt
@@ -14,19 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.camera.core;
+package androidx.compose.ui.test
 
-import static java.lang.annotation.RetentionPolicy.CLASS;
+import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
 
-import androidx.annotation.RequiresOptIn;
+internal actual fun createPlatformTestContext(): PlatformTestContext = PlatformTestContext()
 
-import java.lang.annotation.Retention;
-
-/**
- * Denotes that the annotated method uses an experimental path for retrieving
- * {@link androidx.camera.core.CameraInfo}s.
- */
-@Retention(CLASS)
-@RequiresOptIn
-public @interface ExperimentalCameraInfo {
-}
+internal actual class PlatformTestContext(
+    var accessibilityValidator: AccessibilityValidator? = null
+)
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
index 2414f46..dc39e22 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
@@ -55,7 +55,9 @@
  * @return The [SemanticsNodeInteraction] that is the receiver of this method
  */
 fun SemanticsNodeInteraction.performClick(): SemanticsNodeInteraction {
-    @OptIn(ExperimentalTestApi::class) return this.invokeGlobalAssertions().performClickImpl()
+    // invokeGlobalAssertions() and tryPerformAccessibilityChecks() will be called from the
+    // implementation that uses performTouchInput or performMouseInput
+    return performClickImpl()
 }
 
 /**
@@ -74,6 +76,7 @@
  */
 fun SemanticsNodeInteraction.performScrollTo(): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     do {
         val shouldContinueScroll =
             fetchSemanticsNode("Action performScrollTo() failed.")
@@ -155,6 +158,7 @@
  */
 fun SemanticsNodeInteraction.performScrollToIndex(index: Int): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     fetchSemanticsNode("Failed: performScrollToIndex($index)").scrollToIndex(index, this)
     return this
 }
@@ -184,6 +188,7 @@
  */
 fun SemanticsNodeInteraction.performScrollToKey(key: Any): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed: performScrollToKey(\"$key\")")
     requireSemantics(node, IndexForKey, ScrollToIndex) {
         "Failed to scroll to the item identified by \"$key\""
@@ -231,6 +236,7 @@
     matcher: SemanticsMatcher
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = scrollToMatchingDescendantOrReturnScrollable(matcher) ?: return this
     // If this is NOT a lazy list, but we haven't found the node above ..
     if (!node.isLazyList) {
@@ -379,6 +385,7 @@
     block: TouchInjectionScope.() -> Unit
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed to inject touch input.")
     with(MultiModalInjectionScopeImpl(node, testContext)) {
         try {
@@ -428,6 +435,7 @@
     block: MouseInjectionScope.() -> Unit
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed to inject mouse input.")
     with(MultiModalInjectionScopeImpl(node, testContext)) {
         try {
@@ -467,6 +475,7 @@
     block: KeyInjectionScope.() -> Unit
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed to inject key input.")
     with(MultiModalInjectionScopeImpl(node, testContext)) {
         try {
@@ -615,6 +624,7 @@
     block: RotaryInjectionScope.() -> Unit
 ): SemanticsNodeInteraction {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode("Failed to send rotary Event")
     with(MultiModalInjectionScopeImpl(node, testContext)) {
         try {
@@ -697,6 +707,9 @@
 fun SemanticsNodeInteraction.performFirstLinkClick(
     predicate: (AnnotatedString.Range<LinkAnnotation>) -> Boolean = { true }
 ): SemanticsNodeInteraction {
+    @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
+
     val errorMessage = "Failed to click the link."
     val node = fetchSemanticsNode(errorMessage)
 
@@ -715,6 +728,30 @@
     return this
 }
 
+/**
+ * Tries to perform accessibility checks on the current screen. This will only actually do something
+ * if (1) accessibility checks are enabled and (2) accessibility checks are implemented for the
+ * platform on which the test runs.
+ *
+ * @throws [AssertionError] if accessibility problems are found
+ */
+expect fun SemanticsNodeInteraction.tryPerformAccessibilityChecks(): SemanticsNodeInteraction
+
+/**
+ * Tries to perform accessibility checks on the current screen. This will only actually do something
+ * if (1) accessibility checks are enabled and (2) accessibility checks are implemented for the
+ * platform on which the test runs.
+ *
+ * @throws [AssertionError] if accessibility problems are found
+ */
+fun SemanticsNodeInteractionCollection.tryPerformAccessibilityChecks():
+    SemanticsNodeInteractionCollection {
+    // Accessibility checks don't run on one node only, they run on the whole hierarchy. It doesn't
+    // matter where we start, so just run them on the first node.
+    onFirst().tryPerformAccessibilityChecks()
+    return this
+}
+
 private fun SemanticsNode.isLink(): Boolean {
     return config.contains(SemanticsProperties.LinkTestMarker)
 }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
index 887c9b6..d08438a 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
@@ -20,6 +20,8 @@
 import androidx.compose.ui.unit.Density
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestDispatcher
 
 /**
  * Sets up the test environment, runs the given [test][block] and then tears down the test
@@ -38,7 +40,9 @@
  * Keeping a reference to the [ComposeUiTest] outside of this function is an error.
  *
  * @param effectContext The [CoroutineContext] used to run the composition. The context for
- *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
+ *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
+ *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
+ *   used for composition and the [MainTestClock].
  * @param block The test function.
  */
 @ExperimentalTestApi
@@ -181,6 +185,26 @@
      *   doesn't have access to a host to set content in.
      */
     fun setContent(composable: @Composable () -> Unit)
+
+    /**
+     * Enables accessibility checks that will be run before every action that is expected to change
+     * the UI.
+     *
+     * Accessibility checks are platform dependent, refer to the documentation of the platform
+     * specific variant of [ComposeUiTest] to see if it is supported and how you can configure it.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withComposeUiTest_sample
+     * @see disableAccessibilityChecks
+     */
+    fun enableAccessibilityChecks()
+
+    /**
+     * Disables accessibility checks.
+     *
+     * @sample androidx.compose.ui.test.samples.accessibilityChecks_withComposeUiTest_sample
+     * @see enableAccessibilityChecks
+     */
+    fun disableAccessibilityChecks()
 }
 
 /**
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TestContext.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TestContext.kt
index 300e864..a086ef8 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TestContext.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TestContext.kt
@@ -33,4 +33,18 @@
      * actual object.
      */
     internal val states = mutableIntObjectMapOf<InputDispatcherState>()
+
+    /** Platform specific additions to the [TestContext]. */
+    internal val platform = createPlatformTestContext()
 }
+
+/** Factory method to create a [PlatformTestContext] */
+internal expect fun createPlatformTestContext(): PlatformTestContext
+
+/**
+ * An extension to [TestContext] that allows us to add platform specific context to [TestContext].
+ *
+ * For example, on Android it contains an AccessibilityValidator that is used by Android's
+ * implementation of [tryPerformAccessibilityChecks].
+ */
+internal expect class PlatformTestContext
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TextActions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TextActions.kt
index 2ebac43..8ff274d 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TextActions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/TextActions.kt
@@ -37,6 +37,7 @@
  */
 fun SemanticsNodeInteraction.performTextInput(text: String) {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     getNodeAndFocus()
     performSemanticsAction(SemanticsActions.InsertTextAtCursor) { it(AnnotatedString(text)) }
 }
@@ -83,6 +84,7 @@
     assert(hasPerformImeAction()) { errorOnFail }
     assert(!hasImeAction(ImeAction.Default)) { errorOnFail }
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = getNodeAndFocus(errorOnFail, requireEditable = false)
 
     wrapAssertionErrorsWithNodeInfo(selector, node) {
@@ -103,6 +105,7 @@
     requireEditable: Boolean = true
 ): SemanticsNode {
     @OptIn(ExperimentalTestApi::class) invokeGlobalAssertions()
+    tryPerformAccessibilityChecks()
     val node = fetchSemanticsNode(errorOnFail)
     assert(isEnabled()) { errorOnFail }
     assert(hasRequestFocusAction()) { errorOnFail }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
index 50c0cb7..37c58c3 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
@@ -20,16 +20,14 @@
 import kotlin.coroutines.AbstractCoroutineContextElement
 import kotlin.coroutines.ContinuationInterceptor
 import kotlinx.coroutines.Delay
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.test.TestDispatcher
 
 /**
- * A [ContinuationInterceptor] that wraps another interceptor and implements [Delay]. If the wrapped
- * interceptor also implements [Delay], the delay implementation is delegated to it, otherwise it's
- * delegated to the default delay implementation (i.e. [Dispatchers.Default]). It is necessary that
- * interceptors used in tests, with one of the [TestDispatcher]s, propagate delay like this in order
- * to work with the delay skipping that those dispatchers perform.
+ * A [ContinuationInterceptor] that wraps another interceptor and implements [Delay] by delegating
+ * to the wrapped interceptor. It is necessary that interceptors used in tests, with one of the
+ * [TestDispatcher]s, propagate delay like this in order to work with the delay skipping that those
+ * dispatchers perform.
  */
 // TODO(b/263369561): avoid InternalCoroutinesApi - it is not expected that Delay gain a method but
 // if it ever did this would have potential runtime crashes for tests. Medium term we will leave
@@ -38,10 +36,13 @@
 @OptIn(InternalCoroutinesApi::class)
 @InternalTestApi
 abstract class DelayPropagatingContinuationInterceptorWrapper(
-    wrappedInterceptor: ContinuationInterceptor?
+    wrappedInterceptor: ContinuationInterceptor
 ) :
     AbstractCoroutineContextElement(ContinuationInterceptor),
     ContinuationInterceptor,
     // Coroutines will internally use the Default dispatcher as the delay if the
     // ContinuationInterceptor does not implement Delay.
-    Delay by ((wrappedInterceptor as? Delay) ?: (Dispatchers.Default as Delay))
+    Delay by ((wrappedInterceptor as? Delay)
+        ?: error(
+            "wrappedInterceptor of DelayPropagatingContinuationInterceptorWrapper must implement Delay"
+        ))
diff --git a/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/Actions.commonStubs.kt b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/Actions.commonStubs.kt
index 088a152..03cdf37 100644
--- a/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/Actions.commonStubs.kt
+++ b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/Actions.commonStubs.kt
@@ -18,3 +18,6 @@
 
 internal actual fun SemanticsNodeInteraction.performClickImpl(): SemanticsNodeInteraction =
     implementedInJetBrainsFork()
+
+actual fun SemanticsNodeInteraction.tryPerformAccessibilityChecks(): SemanticsNodeInteraction =
+    implementedInJetBrainsFork()
diff --git a/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/ComposeUiTest.commonStubs.kt b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/ComposeUiTest.commonStubs.kt
index b2b1aea..5d4d559 100644
--- a/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/ComposeUiTest.commonStubs.kt
+++ b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/ComposeUiTest.commonStubs.kt
@@ -50,4 +50,8 @@
     actual fun unregisterIdlingResource(idlingResource: IdlingResource)
 
     actual fun setContent(composable: @Composable () -> Unit)
+
+    actual fun enableAccessibilityChecks()
+
+    actual fun disableAccessibilityChecks()
 }
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/TestContext.commonStubs.kt
similarity index 70%
rename from room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt
rename to compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/TestContext.commonStubs.kt
index fb1781e..ecaf69e 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt
+++ b/compose/ui/ui-test/src/commonStubsMain/kotlin/androidx/compose/ui/test/TestContext.commonStubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.room.paging
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.compose.ui.test
+
+internal actual fun createPlatformTestContext(): PlatformTestContext = implementedInJetBrainsFork()
+
+internal actual class PlatformTestContext
diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
index d77b90b..3d97309 100644
--- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
@@ -31,7 +31,7 @@
  * continuation needs to be dispatched.
  */
 @OptIn(InternalTestApi::class)
-internal class FrameDeferringContinuationInterceptor(parentInterceptor: ContinuationInterceptor?) :
+internal class FrameDeferringContinuationInterceptor(parentInterceptor: ContinuationInterceptor) :
     DelayPropagatingContinuationInterceptorWrapper(parentInterceptor) {
     private val parentDispatcher = parentInterceptor as? CoroutineDispatcher
     private val toRunTrampolined = ArrayDeque<TrampolinedTask<*>>()
diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
index 6e84196..bd1ca2b 100644
--- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
@@ -39,7 +39,7 @@
  * [coroutineScope] contain the test dispatcher controlled by [delayController].
  *
  * @param coroutineScope The [CoroutineScope] used to simulate the main thread and schedule frames
- *   on. It must contain a [TestCoroutineScheduler].
+ *   on. It must contain a [TestCoroutineScheduler] and a [ContinuationInterceptor].
  * @param frameDelayNanos The number of nanoseconds to [delay] between executing frames.
  * @param onPerformTraversals Called with the frame time of the frame that was just executed, after
  *   running all `withFrameNanos` callbacks, but before resuming their callers' continuations. Any
@@ -60,9 +60,13 @@
 ) : MonotonicFrameClock {
     private val delayController =
         requireNotNull(coroutineScope.coroutineContext[TestCoroutineScheduler]) {
-            "coroutineScope should have TestCoroutineScheduler"
+            "TestMonotonicFrameClock's coroutineScope must have a TestCoroutineScheduler"
         }
-    private val parentInterceptor = coroutineScope.coroutineContext[ContinuationInterceptor]
+    // The parentInterceptor resolves to the TestDispatcher
+    private val parentInterceptor =
+        requireNotNull(coroutineScope.coroutineContext[ContinuationInterceptor]) {
+            "TestMonotonicFrameClock's coroutineScope must have a ContinuationInterceptor"
+        }
     private val lock = Any()
     private var awaiters = mutableListOf<(Long) -> Unit>()
     private var spareAwaiters = mutableListOf<(Long) -> Unit>()
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextInputServiceTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextInputServiceTest.kt
index 3fcb905..aa82ff2 100644
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextInputServiceTest.kt
+++ b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextInputServiceTest.kt
@@ -132,6 +132,22 @@
         verify(platformService, never()).stopInput()
     }
 
+    @OptIn(InternalTextApi::class)
+    @Test
+    fun stopInput_removes_the_current_session() {
+        val platformService = mock<PlatformTextInputService>()
+
+        val textInputService = TextInputService(platformService)
+
+        textInputService.startInput()
+
+        assertThat(textInputService.currentInputSession).isNotNull()
+
+        textInputService.stopInput()
+
+        assertThat(textInputService.currentInputSession).isNull()
+    }
+
     @Test
     fun showSoftwareKeyboard_with_valid_session() {
         val platformService = mock<PlatformTextInputService>()
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
index 405ca04..5cbc366 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.ui.text
 
-import androidx.collection.SieveCache
+import androidx.collection.LruCache
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.text.font.FontFamily
@@ -360,10 +360,10 @@
  */
 internal class TextLayoutCache(capacity: Int = DefaultCacheSize) {
     // Do not allocate an LRU cache if the size is just 1.
-    private val cache: SieveCache<CacheTextLayoutInput, TextLayoutResult>? =
+    private val cache: LruCache<CacheTextLayoutInput, TextLayoutResult>? =
         if (capacity != 1) {
-            // 0 or negative cache size is also handled by SieveCache.
-            SieveCache(capacity, capacity)
+            // 0 or negative cache size is also handled by LruCache.
+            LruCache(capacity)
         } else {
             null
         }
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
index 70d309d..3f6dffb 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
@@ -93,6 +93,8 @@
     @InternalTextApi
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     fun stopInput() {
+        // This is a direct stop call, there's no need to compare the current input session.
+        _currentInputSession.set(null)
         platformTextInputService.stopInput()
     }
 
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index fc69f8e..08d8d65 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -308,11 +308,11 @@
     method @androidx.compose.runtime.Stable public inline operator int component1();
     method @androidx.compose.runtime.Stable public inline operator int component2();
     method @androidx.compose.runtime.Stable public operator long div(int other);
-    method public int getHeight();
-    method public int getWidth();
+    method public inline int getHeight();
+    method public inline int getWidth();
     method @androidx.compose.runtime.Stable public operator long times(int other);
-    property @androidx.compose.runtime.Stable public final int height;
-    property @androidx.compose.runtime.Stable public final int width;
+    property @androidx.compose.runtime.Stable public final inline int height;
+    property @androidx.compose.runtime.Stable public final inline int width;
     field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
   }
 
@@ -322,7 +322,7 @@
   }
 
   public final class IntSizeKt {
-    method @androidx.compose.runtime.Stable public static long IntSize(int width, int height);
+    method @androidx.compose.runtime.Stable public static inline long IntSize(int width, int height);
     method public static long getCenter(long);
     method @androidx.compose.runtime.Stable public static long roundToIntSize(long);
     method @androidx.compose.runtime.Stable public static inline operator long times(int, long size);
@@ -370,7 +370,7 @@
     method public static long getSp(float);
     method public static long getSp(int);
     method public static inline boolean isSpecified(long);
-    method public static boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.TextUnit> block);
     method @androidx.compose.runtime.Stable public static inline operator long times(double, long other);
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 80d97ca..8732633 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -305,14 +305,15 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntSize {
+    ctor @kotlin.PublishedApi internal IntSize(@kotlin.PublishedApi long packedValue);
     method @androidx.compose.runtime.Stable public inline operator int component1();
     method @androidx.compose.runtime.Stable public inline operator int component2();
     method @androidx.compose.runtime.Stable public operator long div(int other);
-    method public int getHeight();
-    method public int getWidth();
+    method public inline int getHeight();
+    method public inline int getWidth();
     method @androidx.compose.runtime.Stable public operator long times(int other);
-    property @androidx.compose.runtime.Stable public final int height;
-    property @androidx.compose.runtime.Stable public final int width;
+    property @androidx.compose.runtime.Stable public final inline int height;
+    property @androidx.compose.runtime.Stable public final inline int width;
     field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
   }
 
@@ -322,7 +323,7 @@
   }
 
   public final class IntSizeKt {
-    method @androidx.compose.runtime.Stable public static long IntSize(int width, int height);
+    method @androidx.compose.runtime.Stable public static inline long IntSize(int width, int height);
     method public static long getCenter(long);
     method @androidx.compose.runtime.Stable public static long roundToIntSize(long);
     method @androidx.compose.runtime.Stable public static inline operator long times(int, long size);
@@ -374,7 +375,7 @@
     method public static long getSp(float);
     method public static long getSp(int);
     method public static inline boolean isSpecified(long);
-    method public static boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @kotlin.PublishedApi internal static long pack(long unitType, float v);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.TextUnit> block);
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
index 9ba8646..48c1d7d 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
@@ -27,20 +27,22 @@
 import androidx.compose.ui.util.unpackInt2
 
 /** Constructs an [IntSize] from width and height [Int] values. */
-@Stable fun IntSize(width: Int, height: Int): IntSize = IntSize(packInts(width, height))
+@Stable inline fun IntSize(width: Int, height: Int): IntSize = IntSize(packInts(width, height))
 
 /** A two-dimensional size class used for measuring in [Int] pixels. */
 @Immutable
 @kotlin.jvm.JvmInline
-value class IntSize internal constructor(@PublishedApi internal val packedValue: Long) {
+value class IntSize
+@PublishedApi
+internal constructor(@PublishedApi internal val packedValue: Long) {
     /** The horizontal aspect of the size in [Int] pixels. */
     @Stable
-    val width: Int
+    inline val width: Int
         get() = unpackInt1(packedValue)
 
     /** The vertical aspect of the size in [Int] pixels. */
     @Stable
-    val height: Int
+    inline val height: Int
         get() = unpackInt2(packedValue)
 
     @Stable inline operator fun component1(): Int = width
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/TextUnit.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/TextUnit.kt
index 73bd40b..9f74875 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/TextUnit.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/TextUnit.kt
@@ -243,8 +243,8 @@
 
 /** `true` when this is [TextUnit.Unspecified]. */
 @Stable
-val TextUnit.isUnspecified: Boolean
-    get() = rawType == UNIT_TYPE_UNSPECIFIED
+inline val TextUnit.isUnspecified: Boolean
+    get() = rawType == 0x0L // UNIT_TYPE_UNSPECIFIED
 
 /**
  * If this [TextUnit] [isSpecified] then this is returned, otherwise [block] is executed and its
@@ -320,27 +320,27 @@
 
 @PublishedApi
 internal fun pack(unitType: Long, v: Float): TextUnit =
-    TextUnit(unitType or (v.toBits().toLong() and 0xFFFF_FFFFL))
+    TextUnit(unitType or (v.toRawBits().toLong() and 0xFFFF_FFFFL))
 
 @PublishedApi
 internal fun checkArithmetic(a: TextUnit) {
-    require(!a.isUnspecified) { "Cannot perform operation for Unspecified type." }
+    requirePrecondition(!a.isUnspecified) { "Cannot perform operation for Unspecified type." }
 }
 
 @PublishedApi
 internal fun checkArithmetic(a: TextUnit, b: TextUnit) {
-    require(!a.isUnspecified && !b.isUnspecified) {
+    requirePrecondition(!a.isUnspecified && !b.isUnspecified) {
         "Cannot perform operation for Unspecified type."
     }
-    require(a.type == b.type) { "Cannot perform operation for ${a.type} and ${b.type}" }
+    requirePrecondition(a.type == b.type) { "Cannot perform operation for ${a.type} and ${b.type}" }
 }
 
 @PublishedApi
 internal fun checkArithmetic(a: TextUnit, b: TextUnit, c: TextUnit) {
-    require(!a.isUnspecified && !b.isUnspecified && !c.isUnspecified) {
+    requirePrecondition(!a.isUnspecified && !b.isUnspecified && !c.isUnspecified) {
         "Cannot perform operation for Unspecified type."
     }
-    require(a.type == b.type && b.type == c.type) {
+    requirePrecondition(a.type == b.type && b.type == c.type) {
         "Cannot perform operation for ${a.type} and ${b.type}"
     }
 }
diff --git a/compose/ui/ui-util/api/current.txt b/compose/ui/ui-util/api/current.txt
index 99ab3db..216790ac 100644
--- a/compose/ui/ui-util/api/current.txt
+++ b/compose/ui/ui-util/api/current.txt
@@ -70,6 +70,8 @@
     method public static inline float fastCoerceAtMost(float, float maximumValue);
     method public static inline double fastCoerceIn(double, double minimumValue, double maximumValue);
     method public static inline float fastCoerceIn(float, float minimumValue, float maximumValue);
+    method public static inline boolean fastIsFinite(double);
+    method public static inline boolean fastIsFinite(float);
     method public static inline float fastMaxOf(float a, float b, float c, float d);
     method public static inline float fastMinOf(float a, float b, float c, float d);
     method public static float lerp(float start, float stop, float fraction);
diff --git a/compose/ui/ui-util/api/restricted_current.txt b/compose/ui/ui-util/api/restricted_current.txt
index 99ab3db..216790ac 100644
--- a/compose/ui/ui-util/api/restricted_current.txt
+++ b/compose/ui/ui-util/api/restricted_current.txt
@@ -70,6 +70,8 @@
     method public static inline float fastCoerceAtMost(float, float maximumValue);
     method public static inline double fastCoerceIn(double, double minimumValue, double maximumValue);
     method public static inline float fastCoerceIn(float, float minimumValue, float maximumValue);
+    method public static inline boolean fastIsFinite(double);
+    method public static inline boolean fastIsFinite(float);
     method public static inline float fastMaxOf(float a, float b, float c, float d);
     method public static inline float fastMinOf(float a, float b, float c, float d);
     method public static float lerp(float start, float stop, float fraction);
diff --git a/compose/ui/ui-util/src/androidUnitTest/kotlin/androidx/compose/ui/util/MathHelpersTest.kt b/compose/ui/ui-util/src/androidUnitTest/kotlin/androidx/compose/ui/util/MathHelpersTest.kt
index cfee518..0e0ba74 100644
--- a/compose/ui/ui-util/src/androidUnitTest/kotlin/androidx/compose/ui/util/MathHelpersTest.kt
+++ b/compose/ui/ui-util/src/androidUnitTest/kotlin/androidx/compose/ui/util/MathHelpersTest.kt
@@ -24,6 +24,7 @@
 import kotlin.math.cos
 import kotlin.math.sin
 import kotlin.test.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -191,4 +192,53 @@
             i++
         }
     }
+
+    @Test
+    fun testIsFinite() {
+        assertFalse(Float.NaN.fastIsFinite())
+
+        assertFalse(Float.POSITIVE_INFINITY.fastIsFinite())
+        assertFalse(Float.NEGATIVE_INFINITY.fastIsFinite())
+
+        assertTrue(0.0f.fastIsFinite())
+        assertTrue((-0.0f).fastIsFinite())
+
+        assertTrue(1.0f.fastIsFinite())
+        assertTrue((-1.0f).fastIsFinite())
+
+        assertTrue(16.0f.fastIsFinite())
+        assertTrue((-16.0f).fastIsFinite())
+
+        assertTrue(2037.0f.fastIsFinite())
+        assertTrue((-2037.0f).fastIsFinite())
+
+        assertTrue(Float.MAX_VALUE.isFinite())
+        assertTrue((-Float.MAX_VALUE).isFinite())
+
+        assertTrue(Float.MIN_VALUE.isFinite())
+        assertTrue((-Float.MIN_VALUE).isFinite())
+
+        assertFalse(Double.NaN.fastIsFinite())
+
+        assertFalse(Double.POSITIVE_INFINITY.fastIsFinite())
+        assertFalse(Double.NEGATIVE_INFINITY.fastIsFinite())
+
+        assertTrue(0.0.fastIsFinite())
+        assertTrue((-0.0).fastIsFinite())
+
+        assertTrue(1.0.fastIsFinite())
+        assertTrue((-1.0).fastIsFinite())
+
+        assertTrue(16.0.fastIsFinite())
+        assertTrue((-16.0).fastIsFinite())
+
+        assertTrue(2037.0.fastIsFinite())
+        assertTrue((-2037.0).fastIsFinite())
+
+        assertTrue(Double.MAX_VALUE.isFinite())
+        assertTrue((-Double.MAX_VALUE).isFinite())
+
+        assertTrue(Double.MIN_VALUE.isFinite())
+        assertTrue((-Double.MIN_VALUE).isFinite())
+    }
 }
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
index 7ab60f5..fa82086 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
@@ -93,6 +93,19 @@
 }
 
 /**
+ * Returns `true` if this float is a finite floating-point value; returns `false` otherwise (for
+ * `NaN` and infinity).
+ */
+inline fun Float.fastIsFinite(): Boolean = (toRawBits() and 0x7fffffff) < 0x7f800000
+
+/**
+ * Returns `true` if this double is a finite floating-point value; returns `false` otherwise (for
+ * `NaN` and infinity).
+ */
+inline fun Double.fastIsFinite(): Boolean =
+    (toRawBits() and 0x7fffffff_ffffffffL) < 0x7ff00000_00000000L
+
+/**
  * Fast, approximate cube root function. Returns the cube root of [x]; for any [x] `fastCbrt(-x) ==
  * -fastCbrt(x)`.
  *
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index d00e2c6..cc929b6 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1790,10 +1790,12 @@
     method public androidx.compose.ui.input.pointer.PointerEvent copy(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, android.view.MotionEvent? motionEvent);
     method public int getButtons();
     method public java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> getChanges();
+    method public int getClassification();
     method public int getKeyboardModifiers();
     method public int getType();
     property public final int buttons;
     property public final java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes;
+    property public final int classification;
     property public final int keyboardModifiers;
     property public final int type;
   }
@@ -3187,10 +3189,8 @@
   }
 
   @androidx.compose.runtime.Stable public interface WindowInfo {
-    method public default long getContainerSize();
     method public default int getKeyboardModifiers();
     method public boolean isWindowFocused();
-    property public default long containerSize;
     property public abstract boolean isWindowFocused;
     property public default int keyboardModifiers;
   }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 917483d..2c9b7cf 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1790,10 +1790,12 @@
     method public androidx.compose.ui.input.pointer.PointerEvent copy(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, android.view.MotionEvent? motionEvent);
     method public int getButtons();
     method public java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> getChanges();
+    method public int getClassification();
     method public int getKeyboardModifiers();
     method public int getType();
     property public final int buttons;
     property public final java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes;
+    property public final int classification;
     property public final int keyboardModifiers;
     property public final int type;
   }
@@ -3245,10 +3247,8 @@
   }
 
   @androidx.compose.runtime.Stable public interface WindowInfo {
-    method public default long getContainerSize();
     method public default int getKeyboardModifiers();
     method public boolean isWindowFocused();
-    property public default long containerSize;
     property public abstract boolean isWindowFocused;
     property public default int keyboardModifiers;
   }
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 433b926..8905d36 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -90,7 +90,6 @@
                 api("androidx.lifecycle:lifecycle-runtime-compose:2.8.3")
                 implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
                 implementation("androidx.emoji2:emoji2:1.2.0")
-                implementation("androidx.window:window:1.3.0")
 
                 implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 7a7015f..11e489d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -3090,6 +3090,7 @@
         }
     }
 
+    @FlakyTest(bugId = 356384247)
     @Test
     fun selectionEventBeforeTraverseEvent_whenTraverseText() {
         // Arrange.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index fa6a985..a801e18 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.input.pointer
 
 import android.content.Context
+import android.os.Build
 import android.os.Handler
 import android.os.Looper
 import android.view.InputDevice
@@ -35,11 +36,13 @@
 import android.view.MotionEvent.ACTION_POINTER_UP
 import android.view.MotionEvent.ACTION_SCROLL
 import android.view.MotionEvent.ACTION_UP
+import android.view.MotionEvent.PointerCoords
 import android.view.MotionEvent.TOOL_TYPE_FINGER
 import android.view.MotionEvent.TOOL_TYPE_MOUSE
 import android.view.View
 import android.view.ViewGroup
 import androidx.activity.ComponentActivity
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
@@ -91,6 +94,7 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -1030,9 +1034,7 @@
                     action,
                     1,
                     0,
-                    arrayOf(
-                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
-                    ),
+                    arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_MOUSE }),
                     arrayOf(PointerCoords(pos.x, pos.y, scrollDelta.x, scrollDelta.y))
                 )
 
@@ -1095,9 +1097,7 @@
                     action,
                     1,
                     0,
-                    arrayOf(
-                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_FINGER }
-                    ),
+                    arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_FINGER }),
                     arrayOf(PointerCoords(pos.x, pos.y))
                 )
 
@@ -1497,9 +1497,7 @@
                     ACTION_HOVER_EXIT,
                     1,
                     0,
-                    arrayOf(
-                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
-                    ),
+                    arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_MOUSE }),
                     arrayOf(PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y))
                 )
 
@@ -1510,9 +1508,7 @@
                     ACTION_SCROLL,
                     1,
                     0,
-                    arrayOf(
-                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
-                    ),
+                    arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_MOUSE }),
                     arrayOf(PointerCoords(pos.x, pos.y, scrollDelta.x, scrollDelta.y))
                 )
 
@@ -1530,6 +1526,212 @@
     }
 
     /*
+     * Tests that all valid combinations of MotionEvent.CLASSIFICATION_* are returned from
+     * Compose's [PointerInput].
+     * NOTE 1: We do NOT test invalid MotionEvent Classifications, because you can actually pass an
+     * invalid classification value to [MotionEvent.obtain()] and it is not rejected. Therefore,
+     * to maintain the same behavior, we just return whatever is set in [MotionEvent].
+     * NOTE 2: The [MotionEvent.obtain()] that allows you to set classification, is only available
+     * in U. (Thus, why this test request at least that version.)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    fun motionEventDispatch_withValidClassification_shouldMatchInPointerEvent() {
+        // Skips this test if the SDK is below Android U
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+
+        // --> Arrange
+        var boxLayoutCoordinates: LayoutCoordinates? = null
+        val setUpFinishedLatch = CountDownLatch(1)
+        var motionEventClassification = MotionEvent.CLASSIFICATION_NONE
+        var pointerEvent: PointerEvent? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier.fillMaxSize()
+                        .onGloballyPositioned {
+                            setUpFinishedLatch.countDown()
+                            boxLayoutCoordinates = it
+                        }
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    pointerEvent = awaitPointerEvent()
+                                }
+                            }
+                        }
+                ) {}
+            }
+        }
+
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // Set up values to be used for creation of all MotionEvents.
+        var position: Offset?
+        var eventTime = 0
+        val numPointers = 1
+        val actionIndex = 0
+        val pointerProperties =
+            arrayOf(PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_FINGER })
+        var pointerCoords: Array<PointerCoords>? = null
+        val buttonState = 0
+
+        // --> Act
+        rule.runOnUiThread {
+            // Set up pointerCoords to be used for the rest of the events
+            val root = boxLayoutCoordinates!!.findRootCoordinates()
+            position = root.localPositionOf(boxLayoutCoordinates!!, Offset.Zero)
+            pointerCoords =
+                arrayOf(PointerCoords(position!!.x, position!!.y, Offset.Zero.x, Offset.Zero.y))
+
+            val downEvent =
+                MotionEvent(
+                    eventTime = eventTime,
+                    action = ACTION_DOWN,
+                    numPointers = numPointers,
+                    actionIndex = actionIndex,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords!!,
+                    buttonState = buttonState,
+                    classification = motionEventClassification
+                )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchTouchEvent(downEvent)
+        }
+
+        // --> Assert
+        rule.runOnUiThread {
+            assertThat(pointerEvent).isNotNull()
+            // This will be MotionEvent.CLASSIFICATION_NONE (set in the beginning).
+            assertThat(pointerEvent!!.classification).isEqualTo(motionEventClassification)
+        }
+
+        eventTime += 500
+        motionEventClassification = MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+
+        // --> Act
+        rule.runOnUiThread {
+            val upEvent =
+                MotionEvent(
+                    eventTime = eventTime,
+                    action = ACTION_UP,
+                    numPointers = numPointers,
+                    actionIndex = actionIndex,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords!!,
+                    buttonState = buttonState,
+                    classification = motionEventClassification
+                )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchTouchEvent(upEvent)
+        }
+
+        // --> Assert
+        rule.runOnUiThread {
+            assertThat(pointerEvent).isNotNull()
+            assertThat(pointerEvent!!.classification).isEqualTo(motionEventClassification)
+        }
+
+        eventTime += 500
+        motionEventClassification = MotionEvent.CLASSIFICATION_DEEP_PRESS
+
+        // --> Act
+        rule.runOnUiThread {
+            val downEvent =
+                MotionEvent(
+                    eventTime = eventTime,
+                    action = ACTION_DOWN,
+                    numPointers = numPointers,
+                    actionIndex = actionIndex,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords!!,
+                    buttonState = buttonState,
+                    classification = motionEventClassification
+                )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchTouchEvent(downEvent)
+        }
+
+        // --> Assert
+        rule.runOnUiThread {
+            assertThat(pointerEvent).isNotNull()
+            assertThat(pointerEvent!!.classification).isEqualTo(motionEventClassification)
+        }
+
+        eventTime += 500
+        motionEventClassification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
+
+        // --> Act
+        rule.runOnUiThread {
+            val upEvent =
+                MotionEvent(
+                    eventTime = eventTime,
+                    action = ACTION_UP,
+                    numPointers = numPointers,
+                    actionIndex = actionIndex,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords!!,
+                    buttonState = buttonState,
+                    classification = motionEventClassification
+                )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchTouchEvent(upEvent)
+        }
+
+        // --> Assert
+        rule.runOnUiThread {
+            assertThat(pointerEvent).isNotNull()
+            assertThat(pointerEvent!!.classification).isEqualTo(motionEventClassification)
+        }
+
+        eventTime += 500
+        motionEventClassification = MotionEvent.CLASSIFICATION_PINCH
+
+        // --> Act
+        rule.runOnUiThread {
+            val downEvent =
+                MotionEvent(
+                    eventTime = eventTime,
+                    action = ACTION_DOWN,
+                    numPointers = numPointers,
+                    actionIndex = actionIndex,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords!!,
+                    buttonState = buttonState,
+                    classification = motionEventClassification
+                )
+
+            val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
+            androidComposeView.dispatchTouchEvent(downEvent)
+        }
+
+        // --> Assert
+        rule.runOnUiThread {
+            assertThat(pointerEvent).isNotNull()
+            assertThat(pointerEvent!!.classification).isEqualTo(motionEventClassification)
+        }
+    }
+
+    /*
+     * Tests that [PointerEvent] without a [MotionEvent] will return a NONE classification.
+     */
+    @Test
+    fun pointerInput_withoutMotionEvent_classificationShouldBeNone() {
+        val pointerEventWithoutMotionEvent = PointerEvent(listOf(), internalPointerEvent = null)
+
+        rule.runOnUiThread {
+            assertThat(pointerEventWithoutMotionEvent.classification)
+                .isEqualTo(MotionEvent.CLASSIFICATION_NONE)
+        }
+    }
+
+    /*
      * Tests alternating between hover TOUCH events and touch events across multiple UI elements.
      * Specifically, to recreate Talkback events.
      *
@@ -3551,9 +3753,7 @@
                         ACTION_HOVER_EXIT,
                         1,
                         0,
-                        arrayOf(
-                            PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
-                        ),
+                        arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_MOUSE }),
                         arrayOf(PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y))
                     )
 
@@ -3564,9 +3764,7 @@
                         ACTION_DOWN,
                         1,
                         0,
-                        arrayOf(
-                            PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }
-                        ),
+                        arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_MOUSE }),
                         arrayOf(PointerCoords(pos.x, pos.y, Offset.Zero.x, Offset.Zero.y))
                     )
 
@@ -4273,9 +4471,7 @@
                     ACTION_DOWN,
                     1,
                     0,
-                    arrayOf(
-                        PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_FINGER }
-                    ),
+                    arrayOf(PointerProperties(0).also { it.toolType = TOOL_TYPE_FINGER }),
                     arrayOf(PointerCoords(pos.x, pos.y))
                 )
             androidComposeView.dispatchTouchEvent(down)
@@ -4747,6 +4943,52 @@
     )
 }
 
+/*
+ * Version of MotionEvent() that accepts classification.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private fun MotionEvent(
+    eventTime: Int,
+    action: Int,
+    numPointers: Int,
+    actionIndex: Int,
+    pointerProperties: Array<MotionEvent.PointerProperties>,
+    pointerCoords: Array<MotionEvent.PointerCoords>,
+    buttonState: Int =
+        if (
+            pointerProperties[0].toolType == TOOL_TYPE_MOUSE &&
+                (action == ACTION_DOWN || action == ACTION_MOVE)
+        )
+            MotionEvent.BUTTON_PRIMARY
+        else 0,
+    classification: Int
+): MotionEvent {
+    val source =
+        if (pointerProperties[0].toolType == TOOL_TYPE_MOUSE) {
+            InputDevice.SOURCE_MOUSE
+        } else {
+            InputDevice.SOURCE_TOUCHSCREEN
+        }
+    return MotionEvent.obtain(
+        0,
+        eventTime.toLong(),
+        action + (actionIndex shl ACTION_POINTER_INDEX_SHIFT),
+        numPointers,
+        pointerProperties,
+        pointerCoords,
+        0,
+        buttonState,
+        0f,
+        0f,
+        0,
+        0,
+        source,
+        0,
+        0,
+        classification
+    )!!
+}
+
 internal fun findRootView(view: View): View {
     val parent = view.parent
     if (parent is View) {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
index 5d4e345..e543a0e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
 import androidx.compose.ui.test.junit4.AndroidComposeTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.window.Dialog
 import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupProperties
@@ -271,24 +270,4 @@
         rule.waitForIdle()
         assertThat(keyModifiers.packedValue).isEqualTo(0)
     }
-
-    @Test
-    fun windowInfo_containerSize() {
-        // Arrange.
-        var containerSize = IntSize.Zero
-        var recompositions = 0
-        rule.setContent {
-            BasicText("Main Window")
-            val windowInfo = LocalWindowInfo.current
-            containerSize = windowInfo.containerSize
-            recompositions++
-        }
-
-        // Act.
-        rule.waitForIdle()
-
-        // Assert.
-        assertThat(containerSize).isNotEqualTo(IntSize.Zero)
-        assertThat(recompositions).isEqualTo(1)
-    }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
index cde7762..28fc17c 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
@@ -247,6 +247,8 @@
     @Test
     fun connectionClosed_whenOuterSessionCanceled() {
         setupContent()
+        // keep a strong reference to created InputConnection so it's not collected by GC
+        var ic: InputConnection?
         val sessionJob =
             coroutineScope.launch {
                 try {
@@ -271,7 +273,10 @@
             }
         expect(0)
 
-        rule.runOnIdle { assertThat(hostView.onCreateInputConnection(EditorInfo())).isNotNull() }
+        rule.runOnIdle {
+            ic = hostView.onCreateInputConnection(EditorInfo())
+            assertThat(ic).isNotNull()
+        }
 
         rule.runOnIdle {
             sessionJob.cancel()
@@ -374,6 +379,8 @@
     fun connectionClosed_whenInnerSessionCanceled() {
         setupContent()
         lateinit var sessionJob: Job
+        // keep a strong reference to created InputConnection so it's not collected by GC
+        var ic: InputConnection?
         coroutineScope.launch {
             node1.establishTextInputSession {
                 sessionJob = launch {
@@ -395,7 +402,10 @@
         }
         expect(0)
 
-        rule.runOnIdle { assertThat(hostView.onCreateInputConnection(EditorInfo())).isNotNull() }
+        rule.runOnIdle {
+            ic = hostView.onCreateInputConnection(EditorInfo())
+            assertThat(ic).isNotNull()
+        }
 
         rule.runOnIdle {
             sessionJob.cancel()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
index 0ee2f3f..43bdeff 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
@@ -38,6 +38,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.util.fastIsFinite
 
 /** Converts Android framework [MotionEvent]s into Compose [PointerInputEvent]s. */
 internal class MotionEventAdapter {
@@ -279,7 +280,7 @@
             repeat(historySize) { pos ->
                 val x = getHistoricalX(index, pos)
                 val y = getHistoricalY(index, pos)
-                if (x.isFinite() && y.isFinite()) {
+                if (x.fastIsFinite() && y.fastIsFinite()) {
                     val originalEventPosition = Offset(x, y) // hit path will convert to local
                     val historicalChange =
                         HistoricalChange(
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
index 5160634..cdac70c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
@@ -16,9 +16,16 @@
 
 package androidx.compose.ui.input.pointer
 
+import android.os.Build
 import android.view.KeyEvent
 import android.view.MotionEvent
 import android.view.MotionEvent.ACTION_SCROLL
+import android.view.MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+import android.view.MotionEvent.CLASSIFICATION_DEEP_PRESS
+import android.view.MotionEvent.CLASSIFICATION_NONE
+import android.view.MotionEvent.CLASSIFICATION_PINCH
+import android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
+import androidx.annotation.IntDef
 import androidx.collection.LongSparseArray
 import androidx.compose.ui.util.fastForEach
 
@@ -26,6 +33,22 @@
 
 internal actual typealias NativePointerKeyboardModifiers = Int
 
+/**
+ * Restricts Ints to `MotionEvent`'s classification types. See the
+ * [Android documentation on MotionEvent.getClassification()]
+ * (https://developer.android.com/reference/android/view/MotionEvent#getClassification()) for more
+ * details.
+ */
+@IntDef(
+    CLASSIFICATION_NONE,
+    CLASSIFICATION_AMBIGUOUS_GESTURE,
+    CLASSIFICATION_DEEP_PRESS,
+    CLASSIFICATION_TWO_FINGER_SWIPE,
+    CLASSIFICATION_PINCH
+)
+@Retention(AnnotationRetention.SOURCE) // Only for compile-time checks
+internal annotation class MotionEventClassification
+
 /** Describes a pointer input change event that has occurred at a particular point in time. */
 actual class PointerEvent
 internal actual constructor(
@@ -36,6 +59,16 @@
     internal val motionEvent: MotionEvent?
         get() = internalPointerEvent?.motionEvent
 
+    /** Returns `MotionEvent`'s classification. */
+    @get:MotionEventClassification
+    val classification: Int
+        get() =
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                motionEvent?.classification ?: CLASSIFICATION_NONE
+            } else {
+                CLASSIFICATION_NONE // Return NONE for versions lower than Android Q
+            }
+
     /** @param changes The changes. */
     actual constructor(changes: List<PointerInputChange>) : this(changes, null)
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 76d88bf..e64198f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -188,8 +188,8 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastIsFinite
 import androidx.compose.ui.util.fastLastOrNull
 import androidx.compose.ui.util.fastRoundToInt
 import androidx.compose.ui.util.trace
@@ -209,7 +209,6 @@
 import androidx.lifecycle.findViewTreeLifecycleOwner
 import androidx.savedstate.SavedStateRegistryOwner
 import androidx.savedstate.findViewTreeSavedStateRegistryOwner
-import androidx.window.layout.WindowMetricsCalculator
 import java.lang.reflect.Method
 import java.util.function.Consumer
 import kotlin.coroutines.CoroutineContext
@@ -576,10 +575,7 @@
     // on a different position, but also in the position of each of the grandparents as all these
     // positions add up to final global position)
     private val globalLayoutListener =
-        ViewTreeObserver.OnGlobalLayoutListener {
-            updatePositionCacheAndDispatch()
-            updateWindowMetrics()
-        }
+        ViewTreeObserver.OnGlobalLayoutListener { updatePositionCacheAndDispatch() }
 
     // executed when a scrolling container like ScrollView of RecyclerView performed the scroll,
     // this could affect our global position
@@ -1707,7 +1703,6 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         _windowInfo.isWindowFocused = hasWindowFocus()
-        updateWindowMetrics()
         invalidateLayoutNodeMeasurement(root)
         invalidateLayers(root)
         snapshotObserver.startObserving()
@@ -2227,11 +2222,6 @@
         viewToWindowMatrix.invertTo(windowToViewMatrix)
     }
 
-    private fun updateWindowMetrics() {
-        val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
-        _windowInfo.containerSize = metrics.bounds.toComposeIntSize()
-    }
-
     override fun onCheckIsTextEditor(): Boolean {
         val parentSession =
             textInputSessionMutex.currentSession
@@ -2264,7 +2254,6 @@
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         density = Density(context)
-        updateWindowMetrics()
         if (newConfig.fontWeightAdjustmentCompat != currentFontWeightAdjustment) {
             currentFontWeightAdjustment = newConfig.fontWeightAdjustmentCompat
             fontFamilyResolver = createFontFamilyResolver(context)
@@ -2332,17 +2321,17 @@
 
     private fun isBadMotionEvent(event: MotionEvent): Boolean {
         var eventInvalid =
-            !event.x.isFinite() ||
-                !event.y.isFinite() ||
-                !event.rawX.isFinite() ||
-                !event.rawY.isFinite()
+            !event.x.fastIsFinite() ||
+                !event.y.fastIsFinite() ||
+                !event.rawX.fastIsFinite() ||
+                !event.rawY.fastIsFinite()
 
         if (!eventInvalid) {
             // First event x,y is checked above if block, so we can skip index 0.
             for (index in 1 until event.pointerCount) {
                 eventInvalid =
-                    !event.getX(index).isFinite() ||
-                        !event.getY(index).isFinite() ||
+                    !event.getX(index).fastIsFinite() ||
+                        !event.getY(index).fastIsFinite() ||
                         (SDK_INT >= Q && !isValidMotionEvent(event, index))
 
                 if (eventInvalid) break
@@ -2734,7 +2723,7 @@
 private object MotionEventVerifierApi29 {
     @DoNotInline
     fun isValidMotionEvent(event: MotionEvent, index: Int): Boolean {
-        return event.getRawX(index).isFinite() && event.getRawY(index).isFinite()
+        return event.getRawX(index).fastIsFinite() && event.getRawY(index).fastIsFinite()
     }
 }
 
@@ -2854,5 +2843,3 @@
     )
     return ViewCompatShims.getContentCaptureSession(this)
 }
-
-private fun Rect.toComposeIntSize() = IntSize(width = width(), height = height())
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
index 334a0ad..3b3faab0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.util.fastIsFinite
 import androidx.compose.ui.util.fastRoundToInt
 import kotlin.math.max
 
@@ -347,9 +348,10 @@
         drawContent()
     }
 
-    private fun Size.hasSpecifiedAndFiniteWidth() = this != Size.Unspecified && width.isFinite()
+    private fun Size.hasSpecifiedAndFiniteWidth() = this != Size.Unspecified && width.fastIsFinite()
 
-    private fun Size.hasSpecifiedAndFiniteHeight() = this != Size.Unspecified && height.isFinite()
+    private fun Size.hasSpecifiedAndFiniteHeight() =
+        this != Size.Unspecified && height.fastIsFinite()
 
     override fun toString(): String =
         "PainterModifier(" +
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/HitTestResult.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/HitTestResult.kt
index 9dc1651..e8550a82 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/HitTestResult.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/HitTestResult.kt
@@ -327,7 +327,7 @@
 }
 
 private fun DistanceAndInLayer(distance: Float, isInLayer: Boolean): DistanceAndInLayer {
-    val v1 = distance.toBits().toLong()
+    val v1 = distance.toRawBits().toLong()
     val v2 = if (isInLayer) 1L else 0L
     return DistanceAndInLayer(v1.shl(32) or (v2 and 0xFFFFFFFF))
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
index c4e08a8..86be6a2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.util.fastIsFinite
 
 internal class TailModifierNode : Modifier.Node() {
     init {
@@ -197,7 +198,8 @@
                 hitTestChildren = true
             } else if (
                 isTouchEvent &&
-                    distanceInMinimumTouchTarget(pointerPosition, minimumTouchTargetSize).isFinite()
+                    distanceInMinimumTouchTarget(pointerPosition, minimumTouchTargetSize)
+                        .fastIsFinite()
             ) {
                 inLayer = false
                 hitTestChildren = true
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index ff449d7..b522a01 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -53,6 +53,7 @@
 import androidx.compose.ui.unit.minus
 import androidx.compose.ui.unit.plus
 import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.util.fastIsFinite
 
 /** Measurable and Placeable type that has a position. */
 internal abstract class NodeCoordinator(
@@ -629,7 +630,7 @@
                 val distanceFromEdge =
                     distanceInMinimumTouchTarget(pointerPosition, minimumTouchTargetSize)
                 if (
-                    distanceFromEdge.isFinite() &&
+                    distanceFromEdge.fastIsFinite() &&
                         hitTestResult.isHitInMinimumTouchTargetBetter(distanceFromEdge, false)
                 ) {
                     head.hitNear(
@@ -655,7 +656,7 @@
                 }
 
             if (
-                distanceFromEdge.isFinite() &&
+                distanceFromEdge.fastIsFinite() &&
                     hitTestResult.isHitInMinimumTouchTargetBetter(distanceFromEdge, isInLayer)
             ) {
                 // Hit closer than existing handlers, so just record it
@@ -1048,14 +1049,13 @@
     }
 
     protected fun drawBorder(canvas: Canvas, paint: Paint) {
-        val rect =
-            Rect(
-                left = 0.5f,
-                top = 0.5f,
-                right = measuredSize.width.toFloat() - 0.5f,
-                bottom = measuredSize.height.toFloat() - 0.5f
-            )
-        canvas.drawRect(rect, paint)
+        canvas.drawRect(
+            left = 0.5f,
+            top = 0.5f,
+            right = measuredSize.width.toFloat() - 0.5f,
+            bottom = measuredSize.height.toFloat() - 0.5f,
+            paint = paint
+        )
     }
 
     /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InvertMatrix.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InvertMatrix.kt
index b2e44e7..40d8b46 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InvertMatrix.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/InvertMatrix.kt
@@ -23,6 +23,9 @@
  * failed.
  */
 internal fun Matrix.invertTo(other: Matrix): Boolean {
+    // See comment in Matrix.kt
+    if (values.size < 16 || other.values.size < 16) return false
+
     val a00 = this[0, 0]
     val a01 = this[0, 1]
     val a02 = this[0, 2]
@@ -39,6 +42,7 @@
     val a31 = this[3, 1]
     val a32 = this[3, 2]
     val a33 = this[3, 3]
+
     val b00 = a00 * a11 - a01 * a10
     val b01 = a00 * a12 - a02 * a10
     val b02 = a00 * a13 - a03 * a10
@@ -51,26 +55,26 @@
     val b09 = a21 * a32 - a22 * a31
     val b10 = a21 * a33 - a23 * a31
     val b11 = a22 * a33 - a23 * a32
+
     val det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
-    if (det == 0.0f) {
-        return false
+    if (det != 0.0f) {
+        val invDet = 1.0f / det
+        other[0, 0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
+        other[0, 1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
+        other[0, 2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
+        other[0, 3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
+        other[1, 0] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
+        other[1, 1] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
+        other[1, 2] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
+        other[1, 3] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
+        other[2, 0] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
+        other[2, 1] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
+        other[2, 2] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
+        other[2, 3] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
+        other[3, 0] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
+        other[3, 1] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
+        other[3, 2] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
+        other[3, 3] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
     }
-    val invDet = 1.0f / det
-    other[0, 0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
-    other[0, 1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
-    other[0, 2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
-    other[0, 3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
-    other[1, 0] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
-    other[1, 1] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
-    other[1, 2] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
-    other[1, 3] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
-    other[2, 0] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
-    other[2, 1] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
-    other[2, 2] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
-    other[2, 3] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
-    other[3, 0] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
-    other[3, 1] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
-    other[3, 2] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
-    other[3, 3] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
-    return true
+    return det != 0.0f
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/WindowInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/WindowInfo.kt
index 6cba4e3..d92f726 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/WindowInfo.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/WindowInfo.kt
@@ -24,7 +24,6 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.input.pointer.EmptyPointerKeyboardModifiers
 import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
-import androidx.compose.ui.unit.IntSize
 
 /** Provides information about the Window that is hosting this compose hierarchy. */
 @Stable
@@ -42,10 +41,6 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     val keyboardModifiers: PointerKeyboardModifiers
         get() = WindowInfoImpl.GlobalKeyboardModifiers.value
-
-    /** Size of the window's content container in pixels. */
-    val containerSize: IntSize
-        get() = IntSize.Zero
 }
 
 @Composable
@@ -59,13 +54,12 @@
 
 internal class WindowInfoImpl : WindowInfo {
     private val _isWindowFocused = mutableStateOf(false)
-    private val _containerSize = mutableStateOf(IntSize.Zero)
 
     override var isWindowFocused: Boolean
-        get() = _isWindowFocused.value
         set(value) {
             _isWindowFocused.value = value
         }
+        get() = _isWindowFocused.value
 
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     override var keyboardModifiers: PointerKeyboardModifiers
@@ -74,12 +68,6 @@
             GlobalKeyboardModifiers.value = value
         }
 
-    override var containerSize: IntSize
-        get() = _containerSize.value
-        set(value) {
-            _containerSize.value = value
-        }
-
     companion object {
         // One instance across all windows makes sense, since the state of KeyboardModifiers is
         // common for all windows.
diff --git a/core/core-telecom/api/current.txt b/core/core-telecom/api/current.txt
index 6dd11ef..9cccfad 100644
--- a/core/core-telecom/api/current.txt
+++ b/core/core-telecom/api/current.txt
@@ -2,17 +2,20 @@
 package androidx.core.telecom {
 
   public final class CallAttributesCompat {
-    ctor public CallAttributesCompat(CharSequence displayName, android.net.Uri address, int direction, optional int callType, optional int callCapabilities);
+    ctor public CallAttributesCompat(CharSequence displayName, android.net.Uri address, int direction, optional int callType, optional int callCapabilities, optional androidx.core.telecom.CallEndpointCompat? preferredStartingCallEndpoint);
     method public android.net.Uri getAddress();
     method public int getCallCapabilities();
     method public int getCallType();
     method public int getDirection();
     method public CharSequence getDisplayName();
+    method public androidx.core.telecom.CallEndpointCompat? getPreferredStartingCallEndpoint();
+    method public void setPreferredStartingCallEndpoint(androidx.core.telecom.CallEndpointCompat?);
     property public final android.net.Uri address;
     property public final int callCapabilities;
     property public final int callType;
     property public final int direction;
     property public final CharSequence displayName;
+    property public final androidx.core.telecom.CallEndpointCompat? preferredStartingCallEndpoint;
     field public static final int CALL_TYPE_AUDIO_CALL = 1; // 0x1
     field public static final int CALL_TYPE_VIDEO_CALL = 2; // 0x2
     field public static final androidx.core.telecom.CallAttributesCompat.Companion Companion;
@@ -93,9 +96,11 @@
   public static final class CallException.Companion {
   }
 
-  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager {
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager implements androidx.core.telecom.extensions.CallsManagerExtensions {
     ctor public CallsManager(android.content.Context context);
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? addCallWithExtensions(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.ExtensionInitializationScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> getAvailableStartingCallEndpoints();
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public void registerAppWithTelecom(int capabilities);
     field public static final int CAPABILITY_BASELINE = 1; // 0x1
     field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 4; // 0x4
@@ -106,6 +111,75 @@
   public static final class CallsManager.Companion {
   }
 
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public class InCallServiceCompat extends android.telecom.InCallService implements androidx.core.telecom.extensions.CallExtensions androidx.lifecycle.LifecycleOwner {
+    ctor public InCallServiceCompat();
+    method @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? connectExtensions(android.telecom.Call call, kotlin.jvm.functions.Function1<? super androidx.core.telecom.extensions.CallExtensionScope,kotlin.Unit> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public androidx.lifecycle.Lifecycle lifecycle;
+  }
+
+}
+
+package androidx.core.telecom.extensions {
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface CallExtensionScope {
+    method public androidx.core.telecom.extensions.ParticipantExtensionRemote addParticipantExtension(kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.Participant?,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onActiveParticipantChanged, kotlin.jvm.functions.Function2<? super java.util.Set<? extends androidx.core.telecom.extensions.Participant>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onParticipantsUpdated);
+    method public void onConnected(kotlin.jvm.functions.Function2<? super android.telecom.Call,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block);
+  }
+
+  public interface CallExtensions {
+    method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? connectExtensions(android.telecom.Call call, kotlin.jvm.functions.Function1<? super androidx.core.telecom.extensions.CallExtensionScope,kotlin.Unit> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public interface CallsManagerExtensions {
+    method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? addCallWithExtensions(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.ExtensionInitializationScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ExtensionInitializationScope {
+    method public androidx.core.telecom.extensions.ParticipantExtension addParticipantExtension(optional java.util.Set<androidx.core.telecom.extensions.Participant> initialParticipants, optional androidx.core.telecom.extensions.Participant? initialActiveParticipant);
+    method public void onCall(kotlin.jvm.functions.Function2<? super androidx.core.telecom.CallControlScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onCall);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface KickParticipantAction {
+    method public boolean isSupported();
+    method public suspend Object? requestKickParticipant(androidx.core.telecom.extensions.Participant participant, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public void setSupported(boolean);
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public final class Participant {
+    ctor public Participant(String id, CharSequence name);
+    method public String getId();
+    method public CharSequence getName();
+    property public final String id;
+    property public final CharSequence name;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ParticipantExtension {
+    method public void addKickParticipantSupport(kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.Participant,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onKickParticipant);
+    method public androidx.core.telecom.extensions.RaiseHandState addRaiseHandSupport(optional java.util.List<androidx.core.telecom.extensions.Participant> initialRaisedHands, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onHandRaisedChanged);
+    method public suspend Object? updateActiveParticipant(androidx.core.telecom.extensions.Participant? participant, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? updateParticipants(java.util.Set<androidx.core.telecom.extensions.Participant> newParticipants, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ParticipantExtensionRemote {
+    method public androidx.core.telecom.extensions.KickParticipantAction addKickParticipantAction();
+    method public androidx.core.telecom.extensions.RaiseHandAction addRaiseHandAction(kotlin.jvm.functions.Function2<? super java.util.List<? extends androidx.core.telecom.extensions.Participant>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onRaisedHandsChanged);
+    method public boolean isSupported();
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface RaiseHandAction {
+    method public boolean isSupported();
+    method public suspend Object? requestRaisedHandStateChange(boolean isRaised, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public void setSupported(boolean);
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface RaiseHandState {
+    method public suspend Object? updateRaisedHands(java.util.List<androidx.core.telecom.extensions.Participant> raisedHands, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
 }
 
 package androidx.core.telecom.util {
diff --git a/core/core-telecom/api/restricted_current.txt b/core/core-telecom/api/restricted_current.txt
index 6dd11ef..9cccfad 100644
--- a/core/core-telecom/api/restricted_current.txt
+++ b/core/core-telecom/api/restricted_current.txt
@@ -2,17 +2,20 @@
 package androidx.core.telecom {
 
   public final class CallAttributesCompat {
-    ctor public CallAttributesCompat(CharSequence displayName, android.net.Uri address, int direction, optional int callType, optional int callCapabilities);
+    ctor public CallAttributesCompat(CharSequence displayName, android.net.Uri address, int direction, optional int callType, optional int callCapabilities, optional androidx.core.telecom.CallEndpointCompat? preferredStartingCallEndpoint);
     method public android.net.Uri getAddress();
     method public int getCallCapabilities();
     method public int getCallType();
     method public int getDirection();
     method public CharSequence getDisplayName();
+    method public androidx.core.telecom.CallEndpointCompat? getPreferredStartingCallEndpoint();
+    method public void setPreferredStartingCallEndpoint(androidx.core.telecom.CallEndpointCompat?);
     property public final android.net.Uri address;
     property public final int callCapabilities;
     property public final int callType;
     property public final int direction;
     property public final CharSequence displayName;
+    property public final androidx.core.telecom.CallEndpointCompat? preferredStartingCallEndpoint;
     field public static final int CALL_TYPE_AUDIO_CALL = 1; // 0x1
     field public static final int CALL_TYPE_VIDEO_CALL = 2; // 0x2
     field public static final androidx.core.telecom.CallAttributesCompat.Companion Companion;
@@ -93,9 +96,11 @@
   public static final class CallException.Companion {
   }
 
-  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager {
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager implements androidx.core.telecom.extensions.CallsManagerExtensions {
     ctor public CallsManager(android.content.Context context);
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? addCallWithExtensions(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.ExtensionInitializationScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> getAvailableStartingCallEndpoints();
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public void registerAppWithTelecom(int capabilities);
     field public static final int CAPABILITY_BASELINE = 1; // 0x1
     field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 4; // 0x4
@@ -106,6 +111,75 @@
   public static final class CallsManager.Companion {
   }
 
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public class InCallServiceCompat extends android.telecom.InCallService implements androidx.core.telecom.extensions.CallExtensions androidx.lifecycle.LifecycleOwner {
+    ctor public InCallServiceCompat();
+    method @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? connectExtensions(android.telecom.Call call, kotlin.jvm.functions.Function1<? super androidx.core.telecom.extensions.CallExtensionScope,kotlin.Unit> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public androidx.lifecycle.Lifecycle lifecycle;
+  }
+
+}
+
+package androidx.core.telecom.extensions {
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface CallExtensionScope {
+    method public androidx.core.telecom.extensions.ParticipantExtensionRemote addParticipantExtension(kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.Participant?,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onActiveParticipantChanged, kotlin.jvm.functions.Function2<? super java.util.Set<? extends androidx.core.telecom.extensions.Participant>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onParticipantsUpdated);
+    method public void onConnected(kotlin.jvm.functions.Function2<? super android.telecom.Call,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block);
+  }
+
+  public interface CallExtensions {
+    method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? connectExtensions(android.telecom.Call call, kotlin.jvm.functions.Function1<? super androidx.core.telecom.extensions.CallExtensionScope,kotlin.Unit> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public interface CallsManagerExtensions {
+    method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.core.telecom.util.ExperimentalAppActions public suspend Object? addCallWithExtensions(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onSetInactive, kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.ExtensionInitializationScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> init, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ExtensionInitializationScope {
+    method public androidx.core.telecom.extensions.ParticipantExtension addParticipantExtension(optional java.util.Set<androidx.core.telecom.extensions.Participant> initialParticipants, optional androidx.core.telecom.extensions.Participant? initialActiveParticipant);
+    method public void onCall(kotlin.jvm.functions.Function2<? super androidx.core.telecom.CallControlScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onCall);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface KickParticipantAction {
+    method public boolean isSupported();
+    method public suspend Object? requestKickParticipant(androidx.core.telecom.extensions.Participant participant, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public void setSupported(boolean);
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public final class Participant {
+    ctor public Participant(String id, CharSequence name);
+    method public String getId();
+    method public CharSequence getName();
+    property public final String id;
+    property public final CharSequence name;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ParticipantExtension {
+    method public void addKickParticipantSupport(kotlin.jvm.functions.Function2<? super androidx.core.telecom.extensions.Participant,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onKickParticipant);
+    method public androidx.core.telecom.extensions.RaiseHandState addRaiseHandSupport(optional java.util.List<androidx.core.telecom.extensions.Participant> initialRaisedHands, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onHandRaisedChanged);
+    method public suspend Object? updateActiveParticipant(androidx.core.telecom.extensions.Participant? participant, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? updateParticipants(java.util.Set<androidx.core.telecom.extensions.Participant> newParticipants, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ParticipantExtensionRemote {
+    method public androidx.core.telecom.extensions.KickParticipantAction addKickParticipantAction();
+    method public androidx.core.telecom.extensions.RaiseHandAction addRaiseHandAction(kotlin.jvm.functions.Function2<? super java.util.List<? extends androidx.core.telecom.extensions.Participant>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onRaisedHandsChanged);
+    method public boolean isSupported();
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface RaiseHandAction {
+    method public boolean isSupported();
+    method public suspend Object? requestRaisedHandStateChange(boolean isRaised, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public void setSupported(boolean);
+    property public abstract boolean isSupported;
+  }
+
+  @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface RaiseHandState {
+    method public suspend Object? updateRaisedHands(java.util.List<androidx.core.telecom.extensions.Participant> raisedHands, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
 }
 
 package androidx.core.telecom.util {
diff --git a/core/core-telecom/integration-tests/testapp/src/main/AndroidManifest.xml b/core/core-telecom/integration-tests/testapp/src/main/AndroidManifest.xml
index 25c5e6d..378517a 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/core/core-telecom/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <!-- BLUETOOTH_CONNECT is needed in order to expose the BT device name -->
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
 
     <application
         android:icon="@drawable/ic_launcher"
diff --git a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
index 8e7b013..f4ec9ac 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
+++ b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
@@ -54,7 +54,7 @@
         val disconnectButton: Button = itemView.findViewById(R.id.disconnectButton)
 
         // Call Audio Buttons
-        val earpieceButton: Button = itemView.findViewById(R.id.earpieceButton)
+        val earpieceButton: Button = itemView.findViewById(R.id.selectEndpointButton)
         val speakerButton: Button = itemView.findViewById(R.id.speakerButton)
         val bluetoothButton: Button = itemView.findViewById(R.id.bluetoothButton)
     }
diff --git a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallingMainActivity.kt b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallingMainActivity.kt
index d1303bf..77025be 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallingMainActivity.kt
+++ b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallingMainActivity.kt
@@ -25,6 +25,7 @@
 import android.widget.CheckBox
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.CallsManager
 import androidx.core.view.WindowCompat
 import androidx.recyclerview.widget.LinearLayoutManager
@@ -32,6 +33,7 @@
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 
 @RequiresApi(34)
@@ -44,11 +46,16 @@
     // Telecom
     private var mCallsManager: CallsManager? = null
 
-    // Call Log objects
+    // Ongoing Call List
     private var mRecyclerView: RecyclerView? = null
     private var mCallObjects: ArrayList<CallRow> = ArrayList()
     private lateinit var mAdapter: CallListAdapter
 
+    // Pre-Call Endpoint List
+    private var mPreCallEndpointsRecyclerView: RecyclerView? = null
+    private var mCurrentPreCallEndpoints: ArrayList<CallEndpointCompat> = arrayListOf()
+    private lateinit var mPreCallEndpointAdapter: PreCallEndpointsAdapter
+
     override fun onCreate(savedInstanceState: Bundle?) {
         WindowCompat.setDecorFitsSystemWindows(window, false)
         super.onCreate(savedInstanceState)
@@ -61,6 +68,11 @@
         val registerPhoneAccountButton = findViewById<Button>(R.id.registerButton)
         registerPhoneAccountButton.setOnClickListener { mScope.launch { registerPhoneAccount() } }
 
+        val fetchPreCallEndpointsButton = findViewById<Button>(R.id.preCallAudioEndpointsButton)
+        fetchPreCallEndpointsButton.setOnClickListener {
+            mScope.launch { fetchPreCallEndpoints(findViewById(R.id.cancelFlowButton)) }
+        }
+
         val addOutgoingCallButton = findViewById<Button>(R.id.addOutgoingCall)
         addOutgoingCallButton.setOnClickListener {
             mScope.launch { addCallWithAttributes(Utilities.OUTGOING_CALL_ATTRIBUTES) }
@@ -71,13 +83,17 @@
             mScope.launch { addCallWithAttributes(Utilities.INCOMING_CALL_ATTRIBUTES) }
         }
 
-        // Set up AudioRecord
+        // setup the adapters which hold the endpoint and call rows
         mAdapter = CallListAdapter(mCallObjects, null)
+        mPreCallEndpointAdapter = PreCallEndpointsAdapter(mCurrentPreCallEndpoints)
 
-        // set up the call list view holder
+        // set up the view holders
         mRecyclerView = findViewById(R.id.callListRecyclerView)
         mRecyclerView?.layoutManager = LinearLayoutManager(this)
         mRecyclerView?.adapter = mAdapter
+        mPreCallEndpointsRecyclerView = findViewById(R.id.endpointsRecyclerView)
+        mPreCallEndpointsRecyclerView?.layoutManager = LinearLayoutManager(this)
+        mPreCallEndpointsRecyclerView?.adapter = mPreCallEndpointAdapter
     }
 
     override fun onDestroy() {
@@ -117,15 +133,18 @@
                 Log.i(TAG, "CoroutineExceptionHandler: handling e=$exception")
             }
 
-            CoroutineScope(Dispatchers.IO).launch(handler) {
+            CoroutineScope(Dispatchers.Default).launch(handler) {
                 try {
+                    attributes.preferredStartingCallEndpoint =
+                        mPreCallEndpointAdapter.mSelectedCallEndpoint
                     mCallsManager!!.addCall(
                         attributes,
                         callObject.mOnAnswerLambda,
                         callObject.mOnDisconnectLambda,
                         callObject.mOnSetActiveLambda,
-                        callObject.mOnSetInActiveLambda
+                        callObject.mOnSetInActiveLambda,
                     ) {
+                        mPreCallEndpointAdapter.mSelectedCallEndpoint = null
                         // inject client control interface into the VoIP call object
                         callObject.setCallId(getCallId().toString())
                         callObject.setCallControl(this)
@@ -155,6 +174,29 @@
         }
     }
 
+    private fun fetchPreCallEndpoints(cancelFlowButton: Button) {
+        val endpointsFlow = mCallsManager!!.getAvailableStartingCallEndpoints()
+        CoroutineScope(Dispatchers.Default).launch {
+            launch {
+                val endpointsCoroutineScope = this
+                Log.i(TAG, "fetchEndpoints: consuming endpoints")
+                endpointsFlow.collect {
+                    for (endpoint in it) {
+                        Log.i(TAG, "fetchEndpoints: endpoint=[$endpoint}")
+                    }
+                    cancelFlowButton.setOnClickListener {
+                        mPreCallEndpointAdapter.mSelectedCallEndpoint = null
+                        endpointsCoroutineScope.cancel()
+                        updatePreCallEndpoints(null)
+                    }
+                    updatePreCallEndpoints(it)
+                }
+                // At this point, the endpointsCoroutineScope has been canceled
+                updatePreCallEndpoints(null)
+            }
+        }
+    }
+
     private fun logException(e: Exception, prefix: String) {
         Log.i(TAG, "$prefix: e=[$e], e.msg=[${e.message}], e.stack:${e.printStackTrace()}")
     }
@@ -168,4 +210,14 @@
     private fun updateCallList() {
         runOnUiThread { mAdapter.notifyDataSetChanged() }
     }
+
+    private fun updatePreCallEndpoints(newEndpoints: List<CallEndpointCompat>?) {
+        runOnUiThread {
+            mCurrentPreCallEndpoints.clear()
+            if (newEndpoints != null) {
+                mCurrentPreCallEndpoints.addAll(newEndpoints)
+            }
+            mPreCallEndpointAdapter.notifyDataSetChanged()
+        }
+    }
 }
diff --git a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/PreCallEndpointsAdapter.kt b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/PreCallEndpointsAdapter.kt
new file mode 100644
index 0000000..0ddf351
--- /dev/null
+++ b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/PreCallEndpointsAdapter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallEndpointCompat
+import androidx.recyclerview.widget.RecyclerView
+
+@RequiresApi(26)
+class PreCallEndpointsAdapter(private var mCurrentEndpoints: ArrayList<CallEndpointCompat>?) :
+    RecyclerView.Adapter<PreCallEndpointsAdapter.ViewHolder>() {
+    var mSelectedCallEndpoint: CallEndpointCompat? = null
+
+    companion object {
+        val TAG: String = PreCallEndpointsAdapter::class.java.simpleName
+    }
+
+    class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
+        // TextViews
+        val endpointName: TextView = itemView.findViewById(R.id.endpoint_name)
+        val endpointType: TextView = itemView.findViewById(R.id.endpoint_type_id)
+        val endpointUuid: TextView = itemView.findViewById(R.id.endpoint_uuid_id)
+        // Call State Buttons
+        val selectButton: Button = itemView.findViewById(R.id.select_endpoint_id)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        // inflates the card_view_design view that is used to hold list item
+        val view = LayoutInflater.from(parent.context).inflate(R.layout.endpoint_row, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun getItemCount(): Int {
+        return mCurrentEndpoints?.size ?: 0
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val callEndpointCompat = mCurrentEndpoints?.get(position)
+        if (callEndpointCompat != null) {
+            holder.endpointName.text = callEndpointCompat.name
+            holder.endpointType.text = callEndpointCompat.type.toString()
+            holder.endpointUuid.text = callEndpointCompat.identifier.toString()
+
+            holder.selectButton.setOnClickListener {
+                Log.i(TAG, "selected: preCallEndpoint=[${callEndpointCompat}]")
+                mSelectedCallEndpoint = callEndpointCompat
+            }
+        }
+    }
+}
diff --git a/core/core-telecom/integration-tests/testapp/src/main/res/layout/activity_main.xml b/core/core-telecom/integration-tests/testapp/src/main/res/layout/activity_main.xml
index ae035a5..68efdc9 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/core/core-telecom/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -47,12 +47,14 @@
             android:id="@+id/VideoCallingCheckBox"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:padding="16dp"
             android:text="CAPABILITY_SUPPORTS_VIDEO_CALLING" />
 
         <CheckBox
             android:id="@+id/streamingCheckBox"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="61dp"
+            android:padding="16dp"
             android:text="CAPABILITY_SUPPORTS_CALL_STREAMING" />
 
         <Button
@@ -66,6 +68,32 @@
             app:layout_constraintTop_toTopOf="parent" />
 
         <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/preCallAudioEndpointsButton"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:backgroundTint="#4CAF50"
+                android:text="Fetch Pre-Call Endpoints" />
+
+            <Button
+                android:id="@+id/cancelFlowButton"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:backgroundTint="#D92121"
+                android:text="Stop fetching endpoints" />
+        </LinearLayout>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/endpointsRecyclerView"
+            android:layout_width="match_parent"
+            android:layout_height="164dp" />
+
+        <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="horizontal">
@@ -98,7 +126,7 @@
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/callListRecyclerView"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="273dp"
             tools:itemCount="3" />
 
     </LinearLayout>
diff --git a/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml b/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
index 9001096..ae53266 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
+++ b/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
@@ -17,7 +17,8 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
 
     <LinearLayout
         android:layout_width="wrap_content"
@@ -29,17 +30,17 @@
             android:layout_height="wrap_content"
             android:orientation="horizontal">
 
-        <TextView
-            android:id="@+id/callNumber"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="call # -" />
+            <TextView
+                android:id="@+id/callNumber"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="call # -" />
 
-        <TextView
-            android:id="@+id/callIdTextView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="callId" />
+            <TextView
+                android:id="@+id/callIdTextView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="callId" />
         </LinearLayout>
 
         <LinearLayout
@@ -47,11 +48,11 @@
             android:layout_height="wrap_content"
             android:orientation="horizontal">
 
-        <TextView
-            android:id="@+id/callStateTextView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="currentCallState=[null]; " />
+            <TextView
+                android:id="@+id/callStateTextView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="currentCallState=[null]; " />
 
             <TextView
                 android:id="@+id/endpointStateTextView"
@@ -71,6 +72,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
+                android:backgroundTint="#4CAF50"
                 android:text="Active" />
 
             <Button
@@ -85,6 +87,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
+                android:backgroundTint="#F44336"
                 android:text="Disc." />
         </LinearLayout>
 
@@ -94,7 +97,7 @@
             android:orientation="horizontal">
 
             <Button
-                android:id="@+id/earpieceButton"
+                android:id="@+id/selectEndpointButton"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
diff --git a/core/core-telecom/integration-tests/testapp/src/main/res/layout/endpoint_row.xml b/core/core-telecom/integration-tests/testapp/src/main/res/layout/endpoint_row.xml
new file mode 100644
index 0000000..c4aea89
--- /dev/null
+++ b/core/core-telecom/integration-tests/testapp/src/main/res/layout/endpoint_row.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/endpointType"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/endpoint_name"
+            android:layout_width="319dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="endpointName=[&quot;&quot;]" />
+
+        <TextView
+            android:id="@+id/endpoint_type_id"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="type=[UNKNOWN]" />
+
+        <TextView
+            android:id="@+id/endpoint_uuid_id"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:text="uuid=[null]" />
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/select_endpoint_id"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Select Endpoint" />
+</LinearLayout>
\ No newline at end of file
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
index bcd6d36..3a12399 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
@@ -16,12 +16,14 @@
 
 package androidx.core.telecom.test
 
+import android.media.AudioDeviceInfo
 import android.os.Build.VERSION_CODES
 import android.os.ParcelUuid
 import android.telecom.CallAudioState
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.internal.utils.EndpointUtils
+import androidx.core.telecom.internal.utils.EndpointUtils.Companion.remapAudioDeviceTypeToCallEndpointType
 import androidx.test.filters.SdkSuppress
 import java.util.UUID
 import org.junit.Assert.assertEquals
@@ -113,4 +115,59 @@
         )
         assertEquals(CallAudioState.ROUTE_EARPIECE, EndpointUtils.mapTypeToRoute(-1))
     }
+
+    @SdkSuppress(minSdkVersion = VERSION_CODES.O)
+    @Test
+    fun testAudioDeviceInfoTypeToCallEndpointTypeRemapping() {
+        assertEquals(
+            CallEndpointCompat.TYPE_EARPIECE,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_SPEAKER,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
+        )
+        // Wired Headset Devices
+        assertEquals(
+            CallEndpointCompat.TYPE_WIRED_HEADSET,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_WIRED_HEADSET)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_WIRED_HEADSET,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_WIRED_HEADSET,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_USB_DEVICE)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_WIRED_HEADSET,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_USB_ACCESSORY)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_WIRED_HEADSET,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_USB_HEADSET)
+        )
+        // Bluetooth Devices
+        assertEquals(
+            CallEndpointCompat.TYPE_BLUETOOTH,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_BLUETOOTH,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_HEARING_AID)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_BLUETOOTH,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BLE_HEADSET)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_BLUETOOTH,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BLE_SPEAKER)
+        )
+        assertEquals(
+            CallEndpointCompat.TYPE_BLUETOOTH,
+            remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BLE_BROADCAST)
+        )
+    }
 }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt
new file mode 100644
index 0000000..e326c4f
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.os.Build.VERSION_CODES
+import android.os.ParcelUuid
+import android.telecom.CallAudioState
+import android.telecom.CallEndpoint
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallEndpointCompat
+import androidx.core.telecom.internal.CallChannels
+import androidx.core.telecom.internal.CallSessionLegacy
+import androidx.core.telecom.internal.PreCallEndpoints
+import androidx.core.telecom.internal.utils.EndpointUtils
+import androidx.core.telecom.test.utils.BaseTelecomTest
+import androidx.core.telecom.test.utils.TestUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import java.util.UUID
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = VERSION_CODES.O /* api=26 */)
+@RequiresApi(VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class CallSessionLegacyTest : BaseTelecomTest() {
+    val mEarpieceEndpoint = CallEndpointCompat("EARPIECE", CallEndpoint.TYPE_EARPIECE)
+    val mSpeakerEndpoint = CallEndpointCompat("SPEAKER", CallEndpoint.TYPE_SPEAKER)
+    val mEarAndSpeakerEndpoints = listOf(mEarpieceEndpoint, mSpeakerEndpoint)
+
+    /**
+     * Verify the [CallEndpoint]s echoed from the platform are re-mapped to the existing
+     * [CallEndpointCompat]s the user received with
+     * [androidx.core.telecom.CallsManager#getAvailableStartingCallEndpoints()]
+     */
+    @SmallTest
+    @Test
+    fun testPlatformEndpointsAreRemappedToExistingEndpoints() {
+        setUpBackwardsCompatTest()
+        runBlocking {
+            val callSession =
+                initCallSessionLegacy(
+                    coroutineContext,
+                    null,
+                    PreCallEndpoints(mEarAndSpeakerEndpoints.toMutableList(), Channel())
+                )
+            val supportedRouteMask = CallAudioState.ROUTE_EARPIECE or CallAudioState.ROUTE_SPEAKER
+
+            val platformEndpoints =
+                EndpointUtils.toCallEndpointsCompat(
+                    CallAudioState(false, CallAudioState.ROUTE_EARPIECE, supportedRouteMask)
+                )
+
+            val platformEarpiece = platformEndpoints[0]
+            assertEquals(CallEndpointCompat.TYPE_EARPIECE, platformEarpiece.type)
+            assertEquals(
+                mEarpieceEndpoint,
+                callSession.toRemappedCallEndpointCompat(platformEarpiece)
+            )
+
+            val platformSpeaker = platformEndpoints[1]
+            assertEquals(CallEndpointCompat.TYPE_SPEAKER, platformSpeaker.type)
+            assertEquals(
+                mSpeakerEndpoint,
+                callSession.toRemappedCallEndpointCompat(platformSpeaker)
+            )
+        }
+    }
+
+    private fun initCallSessionLegacy(
+        coroutineContext: CoroutineContext,
+        preferredStartingEndpoint: CallEndpointCompat?,
+        preCallEndpoints: PreCallEndpoints
+    ): CallSessionLegacy {
+        return CallSessionLegacy(
+            getRandomParcelUuid(),
+            TestUtils.INCOMING_CALL_ATTRIBUTES,
+            CallChannels(),
+            coroutineContext,
+            TestUtils.mOnAnswerLambda,
+            TestUtils.mOnDisconnectLambda,
+            TestUtils.mOnSetActiveLambda,
+            TestUtils.mOnSetInActiveLambda,
+            { _, _ -> },
+            preferredStartingEndpoint,
+            preCallEndpoints,
+            CompletableDeferred(Unit),
+        )
+    }
+
+    private fun getRandomParcelUuid(): ParcelUuid {
+        return ParcelUuid.fromString(UUID.randomUUID().toString())
+    }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionTest.kt
index 9dd0e39..2bb2315 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionTest.kt
@@ -23,6 +23,7 @@
 import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.internal.CallChannels
 import androidx.core.telecom.internal.CallSession
+import androidx.core.telecom.internal.PreCallEndpoints
 import androidx.core.telecom.test.utils.BaseTelecomTest
 import androidx.core.telecom.test.utils.TestUtils
 import androidx.core.telecom.util.ExperimentalAppActions
@@ -32,9 +33,11 @@
 import java.util.UUID
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
@@ -50,11 +53,11 @@
 @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
 @RunWith(AndroidJUnit4::class)
 class CallSessionTest : BaseTelecomTest() {
-    val mEarpieceEndpoint = CallEndpointCompat("EARPIECE", CallEndpoint.TYPE_EARPIECE)
-    val mSpeakerEndpoint = CallEndpointCompat("SPEAKER", CallEndpoint.TYPE_SPEAKER)
-    val mBluetoothEndpoint = CallEndpointCompat("BLUETOOTH", CallEndpoint.TYPE_BLUETOOTH)
-    val mEarAndSpeakerEndpoints = listOf(mEarpieceEndpoint, mSpeakerEndpoint)
-    val mEarAndSpeakerAndBtEndpoints =
+    private val mEarpieceEndpoint = CallEndpointCompat("EARPIECE", CallEndpoint.TYPE_EARPIECE)
+    private val mSpeakerEndpoint = CallEndpointCompat("SPEAKER", CallEndpoint.TYPE_SPEAKER)
+    private val mBluetoothEndpoint = CallEndpointCompat("BLUETOOTH", CallEndpoint.TYPE_BLUETOOTH)
+    private val mEarAndSpeakerEndpoints = listOf(mEarpieceEndpoint, mSpeakerEndpoint)
+    private val mEarAndSpeakerAndBtEndpoints =
         listOf(mEarpieceEndpoint, mSpeakerEndpoint, mBluetoothEndpoint)
 
     /**
@@ -152,9 +155,71 @@
         }
     }
 
+    /**
+     * Verify the [CallEndpoint]s echoed from the platform are re-mapped to the existing
+     * [CallEndpointCompat]s the user received with
+     * [androidx.core.telecom.CallsManager#getAvailableStartingCallEndpoints()]
+     */
+    @SdkSuppress(minSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @SmallTest
+    @Test
+    fun testPlatformEndpointsAreRemappedToExistingEndpoints() {
+        setUpV2Test()
+        runBlocking {
+            val callSession =
+                initCallSession(
+                    coroutineContext,
+                    CallChannels(),
+                    PreCallEndpoints(mEarAndSpeakerAndBtEndpoints.toMutableList(), Channel())
+                )
+
+            val platformEarpiece =
+                CallEndpoint(
+                    mEarpieceEndpoint.name,
+                    CallEndpoint.TYPE_EARPIECE,
+                    getRandomParcelUuid()
+                )
+            assertNotEquals(mEarpieceEndpoint.identifier, platformEarpiece.identifier)
+            val platformSpeaker =
+                CallEndpoint(
+                    mSpeakerEndpoint.name,
+                    CallEndpoint.TYPE_SPEAKER,
+                    getRandomParcelUuid()
+                )
+            assertNotEquals(mSpeakerEndpoint.identifier, platformSpeaker.identifier)
+            val platformBt =
+                CallEndpoint(
+                    mBluetoothEndpoint.name,
+                    CallEndpoint.TYPE_BLUETOOTH,
+                    getRandomParcelUuid()
+                )
+            assertNotEquals(mBluetoothEndpoint.identifier, platformBt.identifier)
+
+            val callSessionUuidRemapping = callSession.mJetpackToPlatformCallEndpoint
+            assertEquals(
+                mEarpieceEndpoint,
+                callSession.toRemappedCallEndpointCompat(platformEarpiece)
+            )
+            assertTrue(callSessionUuidRemapping.containsKey(mEarpieceEndpoint.identifier))
+            assertEquals(platformEarpiece, callSessionUuidRemapping[mEarpieceEndpoint.identifier])
+
+            assertEquals(
+                mSpeakerEndpoint,
+                callSession.toRemappedCallEndpointCompat(platformSpeaker)
+            )
+            assertTrue(callSessionUuidRemapping.containsKey(mSpeakerEndpoint.identifier))
+            assertEquals(platformSpeaker, callSessionUuidRemapping[mSpeakerEndpoint.identifier])
+
+            assertEquals(mBluetoothEndpoint, callSession.toRemappedCallEndpointCompat(platformBt))
+            assertTrue(callSessionUuidRemapping.containsKey(mBluetoothEndpoint.identifier))
+            assertEquals(platformBt, callSessionUuidRemapping[mBluetoothEndpoint.identifier])
+        }
+    }
+
     private fun initCallSession(
         coroutineContext: CoroutineContext,
-        callChannels: CallChannels
+        callChannels: CallChannels,
+        preCallEndpoints: PreCallEndpoints? = null,
     ): CallSession {
         return CallSession(
             coroutineContext,
@@ -163,6 +228,7 @@
             TestUtils.mOnDisconnectLambda,
             TestUtils.mOnSetActiveLambda,
             TestUtils.mOnSetInActiveLambda,
+            preCallEndpoints,
             callChannels,
             { _, _ -> },
             CompletableDeferred(Unit)
@@ -170,11 +236,7 @@
     }
 
     fun getCurrentEndpoint(): CallEndpoint {
-        return CallEndpoint(
-            "EARPIECE",
-            CallEndpoint.TYPE_EARPIECE,
-            ParcelUuid.fromString(UUID.randomUUID().toString())
-        )
+        return CallEndpoint("EARPIECE", CallEndpoint.TYPE_EARPIECE, getRandomParcelUuid())
     }
 
     fun getAvailableEndpoint(): List<CallEndpoint> {
@@ -182,4 +244,8 @@
         endpoints.add(getCurrentEndpoint())
         return endpoints
     }
+
+    private fun getRandomParcelUuid(): ParcelUuid {
+        return ParcelUuid.fromString(UUID.randomUUID().toString())
+    }
 }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallsManagerTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallsManagerTest.kt
index cd56b97..1f93cff 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallsManagerTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallsManagerTest.kt
@@ -37,7 +37,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -226,6 +229,104 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @SmallTest
+    @Test
+    fun testEndToEndSelectingAStartingEndpointTransactional() {
+        setUpV2Test()
+        runBlocking { assertStartingCallEndpoint(coroutineContext) }
+    }
+
+    @SdkSuppress(minSdkVersion = VERSION_CODES.O)
+    @SmallTest
+    @Test
+    fun testEndToEndSelectingAStartingEndpointBackwardsCompat() {
+        setUpBackwardsCompatTest()
+        runBlocking { assertStartingCallEndpoint(coroutineContext) }
+    }
+
+    private suspend fun assertStartingCallEndpoint(coroutineContext: CoroutineContext) {
+        mCallsManager.registerAppWithTelecom(
+            CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
+                CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
+        )
+        var preCallEndpointsScope: CoroutineScope? = null
+        try {
+            val endpointsFlow = mCallsManager.getAvailableStartingCallEndpoints()
+
+            val initialEndpointsJob = CompletableDeferred<List<CallEndpointCompat>>()
+            CoroutineScope(coroutineContext).launch {
+                preCallEndpointsScope = this
+                Log.i(TAG, "launched initialEndpointsJob")
+                endpointsFlow.collect {
+                    it.forEach { endpoint ->
+                        Log.i(TAG, "endpointsFlow: collecting endpoint=[$endpoint]")
+                    }
+                    initialEndpointsJob.complete(it)
+                }
+            }
+            Log.i(TAG, "initialEndpointsJob STARTED")
+            initialEndpointsJob.await()
+            Log.i(TAG, "initialEndpointsJob COMPLETED")
+            val initialEndpoints = initialEndpointsJob.getCompleted()
+            val earpieceEndpoint =
+                initialEndpoints.find { it.type == CallEndpointCompat.TYPE_EARPIECE }
+            if (initialEndpoints.size > 1 && earpieceEndpoint != null) {
+                Log.i(TAG, "found 2 endpoints, including TYPE_EARPIECE")
+                TestUtils.OUTGOING_CALL_ATTRIBUTES.preferredStartingCallEndpoint = earpieceEndpoint
+                mCallsManager.addCall(
+                    TestUtils.OUTGOING_CALL_ATTRIBUTES,
+                    TestUtils.mOnAnswerLambda,
+                    TestUtils.mOnDisconnectLambda,
+                    TestUtils.mOnSetActiveLambda,
+                    TestUtils.mOnSetInActiveLambda,
+                ) {
+                    Log.i(TAG, "addCallWithStartingCallEndpoint: running CallControlScope")
+                    launch {
+                        val waitUntilEarpieceEndpointJob = CompletableDeferred<CallEndpointCompat>()
+
+                        val flowsJob = launch {
+                            val earpieceFlow =
+                                currentCallEndpoint.filter {
+                                    Log.i(TAG, "currentCallEndpoint: e=[$it]")
+                                    it.type == CallEndpointCompat.TYPE_EARPIECE
+                                }
+
+                            earpieceFlow.collect {
+                                Log.i(TAG, "earpieceFlow.collect=[$it]")
+                                waitUntilEarpieceEndpointJob.complete(it)
+                            }
+                        }
+
+                        Log.i(TAG, "addCallWithStartingCallEndpoint: before await")
+                        waitUntilEarpieceEndpointJob.await()
+                        Log.i(TAG, "addCallWithStartingCallEndpoint: after await")
+
+                        // at this point, the CallEndpoint has been found
+                        val endpoint = waitUntilEarpieceEndpointJob.getCompleted()
+                        assertNotNull(endpoint)
+                        assertEquals(CallEndpointCompat.TYPE_EARPIECE, endpoint.type)
+
+                        // finally, terminate the call
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                        // stop collecting flows so the test can end
+                        flowsJob.cancel()
+                        Log.i(TAG, " flowsJob.cancel()")
+                    }
+                }
+            } else {
+                Log.i(
+                    TAG,
+                    "assertStartingCallEndpoint: " +
+                        "endpoints.size=[${initialEndpoints.size}], earpiece=[$earpieceEndpoint]"
+                )
+                preCallEndpointsScope?.cancel()
+            }
+        } finally {
+            preCallEndpointsScope?.cancel()
+        }
+    }
+
     suspend fun assertVideoCallStartsWithSpeakerEndpoint() {
         assertWithinTimeout_addCall(
             CallAttributesCompat(
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
index b810a34..b45aab8 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
@@ -24,11 +24,10 @@
 import androidx.core.telecom.CallAttributesCompat
 import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallsManager
-import androidx.core.telecom.extensions.CallExtensionsScope
+import androidx.core.telecom.extensions.CallExtensionScopeImpl
 import androidx.core.telecom.internal.utils.Utils
 import androidx.core.telecom.test.utils.BaseTelecomTest
 import androidx.core.telecom.test.utils.TestUtils
-import androidx.core.telecom.util.ExperimentalAppActions
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -54,11 +53,10 @@
  * or if the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key is present in the call
  * extras (pre-U devices). In the future, this will be expanded to be provide more robust testing to
  * verify binder functionality as well as supporting the case for auto
- * ([CallsManager.EXTRA_VOIP_API_VERSION]).
+ * ([CallExtensionScopeImpl.EXTRA_VOIP_API_VERSION]).
  */
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 @RequiresApi(Build.VERSION_CODES.O)
-@OptIn(ExperimentalAppActions::class)
 @RunWith(AndroidJUnit4::class)
 class E2ECallExtensionExtrasTests : BaseTelecomTest() {
     companion object {
@@ -164,7 +162,7 @@
                         try {
                             val call = TestUtils.waitOnInCallServiceToReachXCalls(ics, 1)
                             Assert.assertNotNull("The returned Call object is <NULL>", call!!)
-                            val extensions = CallExtensionsScope(mContext, this, call)
+                            val extensions = CallExtensionScopeImpl(mContext, this, call)
                             // Assert the call extra or call property from the details
                             assertCallExtraOrProperty(extensions, call)
                         } finally {
@@ -181,9 +179,9 @@
     }
 
     /** Helper to assert the call extra or property set on the call coming from Telecom. */
-    private suspend fun assertCallExtraOrProperty(extensions: CallExtensionsScope, call: Call) {
+    private suspend fun assertCallExtraOrProperty(extensions: CallExtensionScopeImpl, call: Call) {
         val type = extensions.resolveCallExtensionsType()
-        assertEquals(CallExtensionsScope.CAPABILITY_EXCHANGE, type)
+        assertEquals(CallExtensionScopeImpl.CAPABILITY_EXCHANGE, type)
         // Assert the specifics of the extensions are correct. Note, resolveCallExtensionsType also
         // internally assures the details are set properly
         val callDetails = call.details!!
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2EExtensionTests.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2EExtensionTests.kt
index 0109014..b46b7ce 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2EExtensionTests.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2EExtensionTests.kt
@@ -21,15 +21,14 @@
 import android.os.Build
 import android.os.Build.VERSION_CODES
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.InCallServiceCompat
-import androidx.core.telecom.extensions.CallExtensionCreator
-import androidx.core.telecom.extensions.CallExtensionsScope
+import androidx.core.telecom.extensions.CallExtensionScope
 import androidx.core.telecom.extensions.Capability
 import androidx.core.telecom.extensions.Extensions
 import androidx.core.telecom.extensions.Participant
-import androidx.core.telecom.extensions.ParticipantExtension
+import androidx.core.telecom.extensions.ParticipantExtensionImpl
 import androidx.core.telecom.extensions.ParticipantExtensionRemote
-import androidx.core.telecom.internal.CapabilityExchangeListenerRemote
 import androidx.core.telecom.test.VoipAppWithExtensions.VoipAppWithExtensionsControl
 import androidx.core.telecom.test.VoipAppWithExtensions.VoipAppWithExtensionsControlLocal
 import androidx.core.telecom.test.utils.BaseTelecomTest
@@ -42,14 +41,10 @@
 import androidx.test.rule.GrantPermissionRule
 import androidx.test.rule.ServiceTestRule
 import junit.framework.TestCase.assertEquals
-import junit.framework.TestCase.assertNull
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withTimeoutOrNull
-import kotlinx.coroutines.yield
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
@@ -78,11 +73,11 @@
         private val CAPABILITY_PARTICIPANT_WITH_ACTIONS =
             createCapability(
                 id = Extensions.PARTICIPANT,
-                version = ParticipantExtension.VERSION,
+                version = ParticipantExtensionImpl.VERSION,
                 actions =
                     setOf(
-                        ParticipantExtension.RAISE_HAND_ACTION,
-                        ParticipantExtension.KICK_PARTICIPANT_ACTION
+                        ParticipantExtensionImpl.RAISE_HAND_ACTION,
+                        ParticipantExtensionImpl.KICK_PARTICIPANT_ACTION
                     )
             )
 
@@ -110,7 +105,7 @@
         }
     }
 
-    internal class CachedParticipants(scope: CallExtensionsScope) {
+    internal class CachedParticipants(scope: CallExtensionScope) {
         private val participantState = MutableStateFlow<Set<Participant>>(emptySet())
         private val activeParticipantState = MutableStateFlow<Participant?>(null)
         val extension =
@@ -137,10 +132,10 @@
     }
 
     internal class CachedRaisedHands(extension: ParticipantExtensionRemote) {
-        private val raisedHands = MutableStateFlow<Set<Participant>>(emptySet())
+        private val raisedHands = MutableStateFlow<List<Participant>>(emptyList())
         val action = extension.addRaiseHandAction(raisedHands::emit)
 
-        suspend fun waitForRaisedHands(expected: Set<Participant>) {
+        suspend fun waitForRaisedHands(expected: List<Participant>) {
             val result =
                 withTimeoutOrNull(ICS_EXTENSION_UPDATE_TIMEOUT_MS) {
                     raisedHands.first { it == expected }
@@ -207,7 +202,9 @@
             TestUtils.waitOnInCallServiceToReachXCalls(ics, 1)
             try {
                 // Send updateParticipants to ensure there is no error/exception
-                voipAppControl.updateParticipants(listOf(TestUtils.getDefaultParticipant()))
+                voipAppControl.updateParticipants(
+                    listOf(TestUtils.getDefaultParticipantParcelable())
+                )
             } catch (e: Exception) {
                 fail("calling extension methods should not result in any exceptions: Exception: $e")
             }
@@ -215,36 +212,43 @@
     }
 
     /**
-     * Create a new VOIP call and use [InCallServiceCompat.connectExtensions] in the ICS to connect
-     * to the VOIP call. Once complete, use the [CallExtensionsScope.registerExtension] method to
-     * register an unknown extension and ensure we get the correct null indication.
+     * Create a VOIP call with a participants extension and no actions Verify that all of the
+     * participant extension functions work as expected.
      */
     @LargeTest
     @Test(timeout = 10000)
-    fun testIcsExtensionsCreationUnknownCapability() = runBlocking {
+    fun testVoipAndIcsWithParticipants() = runBlocking {
         usingIcs { ics ->
             val voipAppControl = bindToVoipAppWithExtensions()
+            val callback = TestCallCallbackListener(this)
+            voipAppControl.setCallback(callback)
             createAndVerifyVoipCall(
                 voipAppControl,
-                listOf(CAPABILITY_PARTICIPANT_WITH_ACTIONS),
+                listOf(getParticipantCapability(emptySet())),
                 parameters.direction
             )
 
             val call = TestUtils.waitOnInCallServiceToReachXCalls(ics, 1)!!
             var hasConnected = false
-            // Manually connect extensions here to exercise the CallExtensionsScope class
             with(ics) {
                 connectExtensions(call) {
-                    // Create an extension that the VOIP app does not know about and ensure that
-                    // we receive a null response during negotiation so we can notify the ICS of the
-                    // state of that extension
-                    val nonexistentRemote = registerInvalidExtension(this)
+                    val participants = CachedParticipants(this)
                     onConnected {
                         hasConnected = true
-                        assertNull(
-                            "Connection to remote should be null for features with no VOIP support",
-                            nonexistentRemote.await()
+                        // Wait for initial state
+                        participants.waitForParticipants(emptySet())
+                        participants.waitForActiveParticipant(null)
+                        // Test VOIP -> ICS connection by updating state
+                        val currentParticipants = TestUtils.generateParticipants(2)
+                        voipAppControl.updateParticipants(
+                            currentParticipants.map { it.toParticipantParcelable() }
                         )
+                        participants.waitForParticipants(currentParticipants.toSet())
+                        voipAppControl.updateActiveParticipant(
+                            currentParticipants[0].toParticipantParcelable()
+                        )
+                        participants.waitForActiveParticipant(currentParticipants[0])
+
                         call.disconnect()
                     }
                 }
@@ -255,11 +259,11 @@
 
     /**
      * Create a VOIP call with a participants extension and attach participant Call extensions.
-     * Verify that all of the participant extension functions work as expected.
+     * Verify raised hands functionality works as expected
      */
     @LargeTest
     @Test(timeout = 10000)
-    fun testVoipAndIcsWithParticipants() = runBlocking {
+    fun testVoipAndIcsRaisedHands() = runBlocking {
         usingIcs { ics ->
             val voipAppControl = bindToVoipAppWithExtensions()
             val callback = TestCallCallbackListener(this)
@@ -267,7 +271,9 @@
             val voipCallId =
                 createAndVerifyVoipCall(
                     voipAppControl,
-                    listOf(CAPABILITY_PARTICIPANT_WITH_ACTIONS),
+                    listOf(
+                        getParticipantCapability(setOf(ParticipantExtensionImpl.RAISE_HAND_ACTION))
+                    ),
                     parameters.direction
                 )
 
@@ -277,33 +283,83 @@
                 connectExtensions(call) {
                     val participants = CachedParticipants(this)
                     val raiseHandAction = CachedRaisedHands(participants.extension)
-                    val kickParticipantAction = participants.extension.addKickParticipantAction()
                     onConnected {
                         hasConnected = true
-                        // Test VOIP -> ICS connection by updating state
-                        participants.waitForParticipants(emptySet())
-                        participants.waitForActiveParticipant(null)
+                        val currentParticipants = TestUtils.generateParticipants(3)
+                        voipAppControl.updateParticipants(
+                            currentParticipants.map { it.toParticipantParcelable() }
+                        )
+                        participants.waitForParticipants(currentParticipants.toSet())
 
-                        voipAppControl.updateParticipants(listOf(TestUtils.getDefaultParticipant()))
-                        participants.waitForParticipants(setOf(TestUtils.getDefaultParticipant()))
-
-                        voipAppControl.updateActiveParticipant(TestUtils.getDefaultParticipant())
-                        participants.waitForActiveParticipant(TestUtils.getDefaultParticipant())
-
-                        voipAppControl.updateRaisedHands(listOf(TestUtils.getDefaultParticipant()))
-                        raiseHandAction.waitForRaisedHands(setOf(TestUtils.getDefaultParticipant()))
+                        // Reverse the ordering of the list to ensure that ordering is maintained
+                        // across the binder.
+                        val raisedHands = currentParticipants.reversed()
+                        voipAppControl.updateRaisedHands(
+                            raisedHands.map { it.toParticipantParcelable() }
+                        )
+                        raiseHandAction.waitForRaisedHands(raisedHands)
+                        val raisedHandsAndInvalid = ArrayList(raisedHands)
+                        raisedHandsAndInvalid.add(Participant("INVALID", "INVALID"))
+                        voipAppControl.updateRaisedHands(
+                            raisedHandsAndInvalid.map { it.toParticipantParcelable() }
+                        )
+                        // action should not contain the invalid Participant
+                        raiseHandAction.waitForRaisedHands(raisedHands)
 
                         // Test ICS -> VOIP connection by sending events
                         raiseHandAction.action.requestRaisedHandStateChange(true)
                         callback.waitForRaiseHandState(voipCallId, true)
 
-                        kickParticipantAction.requestKickParticipant(
-                            TestUtils.getDefaultParticipant()
+                        call.disconnect()
+                    }
+                }
+            }
+            assertTrue("onConnected never received", hasConnected)
+        }
+    }
+
+    /**
+     * Create a VOIP call with a participants extension and attach participant Call extensions.
+     * Verify kick participant functionality works as expected
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testVoipAndIcsKickParticipant() = runBlocking {
+        usingIcs { ics ->
+            val voipAppControl = bindToVoipAppWithExtensions()
+            val callback = TestCallCallbackListener(this)
+            voipAppControl.setCallback(callback)
+            val voipCallId =
+                createAndVerifyVoipCall(
+                    voipAppControl,
+                    listOf(
+                        getParticipantCapability(
+                            setOf(ParticipantExtensionImpl.KICK_PARTICIPANT_ACTION)
                         )
-                        callback.waitForKickParticipant(
-                            voipCallId,
-                            TestUtils.getDefaultParticipant()
+                    ),
+                    parameters.direction
+                )
+
+            val call = TestUtils.waitOnInCallServiceToReachXCalls(ics, 1)!!
+            var hasConnected = false
+            with(ics) {
+                connectExtensions(call) {
+                    val participants = CachedParticipants(this)
+                    val kickParticipant = participants.extension.addKickParticipantAction()
+                    onConnected {
+                        hasConnected = true
+                        val currentParticipants = TestUtils.generateParticipants(3)
+                        voipAppControl.updateParticipants(
+                            currentParticipants.map { it.toParticipantParcelable() }
                         )
+                        participants.waitForParticipants(currentParticipants.toSet())
+                        // Kick a valid participant
+                        assertEquals(
+                            "Never received response to kickParticipant request",
+                            CallControlResult.Success(),
+                            kickParticipant.requestKickParticipant(currentParticipants[0])
+                        )
+                        callback.waitForKickParticipant(voipCallId, currentParticipants[0])
 
                         call.disconnect()
                     }
@@ -318,22 +374,6 @@
      * Helpers
      * =========================================================================================
      */
-    private fun registerInvalidExtension(
-        scope: CallExtensionsScope,
-    ): CompletableDeferred<CapabilityExchangeListenerRemote?> {
-        val deferredVal = CompletableDeferred<CapabilityExchangeListenerRemote?>()
-        scope.registerExtension {
-            CallExtensionCreator(
-                extensionCapability =
-                    createCapability(id = 8675309, version = 42, actions = emptySet()),
-                onExchangeComplete = { capability, remote ->
-                    assertNull("Expected null capability", capability)
-                    deferredVal.complete(remote)
-                }
-            )
-        }
-        return deferredVal
-    }
 
     /**
      * Creates a VOIP call using the specified capabilities and direction and then verifies that it
@@ -385,19 +425,11 @@
         return ITestAppControl.Stub.asInterface(voipAppServiceRule.bindService(serviceIntent))
     }
 
-    /**
-     * Tests the value returned from the [supplier] using [predicate] and retries until the criteria
-     * is met. Retries every second for up to 5 seconds.
-     */
-    private suspend fun <R> waitForResult(predicate: (R?) -> Boolean, supplier: () -> R): R? {
-        var result = supplier()
-        withTimeoutOrNull(5000) {
-            while (!predicate(result)) {
-                yield()
-                delay(1000)
-                result = supplier()
-            }
-        }
-        return result
+    private fun getParticipantCapability(actions: Set<Int>): Capability {
+        return createCapability(
+            id = Extensions.PARTICIPANT,
+            version = ParticipantExtensionImpl.VERSION,
+            actions = actions
+        )
     }
 }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/JetpackConnectionServiceTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/JetpackConnectionServiceTest.kt
index db5bbd7..8e5dfbe 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/JetpackConnectionServiceTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/JetpackConnectionServiceTest.kt
@@ -255,6 +255,8 @@
                 TestUtils.mOnSetActiveLambda,
                 TestUtils.mOnSetInActiveLambda,
                 TestUtils.mOnEventLambda,
+                null,
+                null,
                 CompletableDeferred()
             )
 
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/PreCallEndpointsTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/PreCallEndpointsTest.kt
new file mode 100644
index 0000000..bd4a102
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/PreCallEndpointsTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallEndpointCompat
+import androidx.core.telecom.internal.PreCallEndpoints
+import androidx.test.filters.SmallTest
+import kotlinx.coroutines.channels.Channel
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@RequiresApi(Build.VERSION_CODES.O)
+class PreCallEndpointsTest {
+    private val defaultEarpiece = CallEndpointCompat("E", CallEndpointCompat.TYPE_EARPIECE)
+    private val defaultSpeaker = CallEndpointCompat("S", CallEndpointCompat.TYPE_SPEAKER)
+    private val defaultBluetooth = CallEndpointCompat("B", CallEndpointCompat.TYPE_BLUETOOTH)
+
+    @SmallTest
+    @Test
+    fun testInitialValues() {
+        val initEndpoints = mutableListOf(defaultEarpiece, defaultSpeaker, defaultBluetooth)
+        val currentPreCallEndpoints = PreCallEndpoints(initEndpoints, Channel())
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultEarpiece))
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultSpeaker))
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultBluetooth))
+        assertTrue(currentPreCallEndpoints.mNonBluetoothEndpoints.contains(defaultEarpiece.type))
+        assertTrue(currentPreCallEndpoints.mNonBluetoothEndpoints.contains(defaultSpeaker.type))
+        assertTrue(currentPreCallEndpoints.mBluetoothEndpoints.contains(defaultBluetooth.name))
+    }
+
+    @SmallTest
+    @Test
+    fun testEndpointsAddedWithNewEndpoint() {
+        val initEndpoints = mutableListOf(defaultEarpiece)
+        val currentPreCallEndpoints = PreCallEndpoints(initEndpoints, Channel())
+
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultEarpiece))
+        assertFalse(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultSpeaker))
+
+        val res = currentPreCallEndpoints.maybeAddCallEndpoint(defaultSpeaker)
+        assertEquals(PreCallEndpoints.START_TRACKING_NEW_ENDPOINT, res)
+    }
+
+    @SmallTest
+    @Test
+    fun testEndpointsAddedWithNoNewEndpoints() {
+        val initEndpoints = mutableListOf(defaultEarpiece, defaultSpeaker)
+        val currentPreCallEndpoints = PreCallEndpoints(initEndpoints, Channel())
+
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultEarpiece))
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultSpeaker))
+
+        val res = currentPreCallEndpoints.maybeAddCallEndpoint(defaultSpeaker)
+        assertEquals(PreCallEndpoints.ALREADY_TRACKING_ENDPOINT, res)
+    }
+
+    @SmallTest
+    @Test
+    fun testEndpointsRemovedWithUntrackedEndpoint() {
+        val initEndpoints = mutableListOf(defaultEarpiece)
+        val currentPreCallEndpoints = PreCallEndpoints(initEndpoints, Channel())
+
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultEarpiece))
+        assertFalse(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultSpeaker))
+
+        val res = currentPreCallEndpoints.maybeRemoveCallEndpoint(defaultSpeaker)
+        assertEquals(PreCallEndpoints.NOT_TRACKING_REMOVED_ENDPOINT, res)
+    }
+
+    @SmallTest
+    @Test
+    fun testEndpointsRemovedWithTrackedEndpoint() {
+        val initEndpoints = mutableListOf(defaultEarpiece, defaultSpeaker)
+        val currentPreCallEndpoints = PreCallEndpoints(initEndpoints, Channel())
+
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultEarpiece))
+        assertTrue(currentPreCallEndpoints.isCallEndpointBeingTracked(defaultSpeaker))
+
+        val res = currentPreCallEndpoints.maybeRemoveCallEndpoint(defaultSpeaker)
+        assertEquals(PreCallEndpoints.STOP_TRACKING_REMOVED_ENDPOINT, res)
+    }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipAppWithExtensionsControl.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipAppWithExtensionsControl.kt
index f030f12..18b590d 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipAppWithExtensionsControl.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipAppWithExtensionsControl.kt
@@ -30,6 +30,8 @@
 import androidx.core.telecom.CallsManager
 import androidx.core.telecom.extensions.Capability
 import androidx.core.telecom.extensions.Participant
+import androidx.core.telecom.extensions.ParticipantParcelable
+import androidx.core.telecom.extensions.toParticipant
 import androidx.core.telecom.test.ITestAppControl
 import androidx.core.telecom.test.ITestAppControlCallback
 import androidx.core.telecom.test.utils.TestUtils
@@ -54,7 +56,7 @@
     private var mCallback: ITestAppControlCallback? = null
     private var participantsFlow: MutableStateFlow<Set<Participant>> = MutableStateFlow(emptySet())
     private var activeParticipantFlow: MutableStateFlow<Participant?> = MutableStateFlow(null)
-    private var raisedHandsFlow: MutableStateFlow<Set<Participant>> = MutableStateFlow(emptySet())
+    private var raisedHandsFlow: MutableStateFlow<List<Participant>> = MutableStateFlow(emptyList())
 
     companion object {
         val TAG = VoipAppWithExtensionsControl::class.java.simpleName
@@ -110,7 +112,7 @@
                                 raisedHandsFlow
                                     .onEach {
                                         TestUtils.printParticipants(it, "VoIP raised hands")
-                                        raiseHandStateUpdater!!.updateRaisedHands(it)
+                                        raiseHandStateUpdater?.updateRaisedHands(it)
                                     }
                                     .launchIn(this)
                                 activeParticipantFlow
@@ -129,16 +131,16 @@
                 return id
             }
 
-            override fun updateParticipants(setOfParticipants: List<Participant>) {
-                participantsFlow.value = setOfParticipants.toSet()
+            override fun updateParticipants(setOfParticipants: List<ParticipantParcelable>) {
+                participantsFlow.value = setOfParticipants.map { it.toParticipant() }.toSet()
             }
 
-            override fun updateActiveParticipant(participant: Participant?) {
-                activeParticipantFlow.value = participant
+            override fun updateActiveParticipant(participant: ParticipantParcelable?) {
+                activeParticipantFlow.value = participant?.toParticipant()
             }
 
-            override fun updateRaisedHands(raisedHandsParticipants: List<Participant>) {
-                raisedHandsFlow.value = raisedHandsParticipants.toSet()
+            override fun updateRaisedHands(raisedHandsParticipants: List<ParticipantParcelable>) {
+                raisedHandsFlow.value = raisedHandsParticipants.map { it.toParticipant() }
             }
         }
 
@@ -157,7 +159,7 @@
         mCallback = null
         participantsFlow.value = emptySet()
         activeParticipantFlow.value = null
-        raisedHandsFlow.value = emptySet()
+        raisedHandsFlow.value = emptyList()
         return false
     }
 
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
index ceea6e2..fc9d6f5 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import android.telecom.DisconnectCause
+import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
 import androidx.core.telecom.CallControlScope
@@ -26,6 +27,7 @@
 import androidx.core.telecom.extensions.ExtensionInitializationScope
 import androidx.core.telecom.extensions.Extensions
 import androidx.core.telecom.extensions.ParticipantExtension
+import androidx.core.telecom.extensions.ParticipantExtensionImpl
 import androidx.core.telecom.extensions.RaiseHandState
 import androidx.core.telecom.test.ITestAppControlCallback
 import androidx.core.telecom.util.ExperimentalAppActions
@@ -37,6 +39,10 @@
     private val callback: ITestAppControlCallback?,
     private val capabilities: List<Capability>
 ) {
+    companion object {
+        private const val TAG = "VoipCall"
+    }
+
     private lateinit var callId: String
     // Participant state updaters
     internal var participantStateUpdater: ParticipantExtension? = null
@@ -50,6 +56,7 @@
         onSetInactive: suspend () -> Unit,
         init: CallControlScope.() -> Unit
     ) {
+        Log.i(TAG, "addCall: capabilities=$capabilities")
         callsManager.addCallWithExtensions(
             callAttributes,
             onAnswer,
@@ -79,13 +86,15 @@
     private fun ParticipantExtension.initializeActions(capability: Capability) {
         for (action in capability.supportedActions) {
             when (action) {
-                ParticipantExtension.RAISE_HAND_ACTION -> {
+                ParticipantExtensionImpl.RAISE_HAND_ACTION -> {
                     raiseHandStateUpdater = addRaiseHandSupport {
                         callback?.raiseHandStateAction(callId, it)
                     }
                 }
-                ParticipantExtension.KICK_PARTICIPANT_ACTION -> {
-                    addKickParticipantSupport { callback?.kickParticipantAction(callId, it) }
+                ParticipantExtensionImpl.KICK_PARTICIPANT_ACTION -> {
+                    addKickParticipantSupport {
+                        callback?.kickParticipantAction(callId, it.toParticipantParcelable())
+                    }
                 }
             }
         }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestInCallService.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestInCallService.kt
index 91f245e..6e23441 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestInCallService.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestInCallService.kt
@@ -61,13 +61,13 @@
         mCalls.remove(call)
     }
 
-    override fun onBind(intent: Intent): IBinder? {
-        if (intent.action == SERVICE_INTERFACE) {
+    override fun onBind(intent: Intent?): IBinder? {
+        if (intent?.action == SERVICE_INTERFACE) {
             Log.i(LOG_TAG, "InCallService bound from telecom")
             mTelecomBoundFlow.tryEmit(true)
             return super.onBind(intent)
         }
-        Log.i(LOG_TAG, "InCallService bound by ${intent.component}")
+        Log.i(LOG_TAG, "InCallService bound by ${intent?.component}")
         return localBinder
     }
 
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
index f2b9829..7548186 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
@@ -31,6 +31,8 @@
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
 import androidx.core.telecom.extensions.Participant
+import androidx.core.telecom.extensions.ParticipantParcelable
+import androidx.core.telecom.extensions.toParticipant
 import androidx.core.telecom.internal.utils.BuildVersionAdapter
 import androidx.core.telecom.test.ITestAppControlCallback
 import androidx.core.telecom.util.ExperimentalAppActions
@@ -375,20 +377,31 @@
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
     }
 
+    /** Generate a List of [Participant]s, where each ID corresponds to a range of 1 to [num] */
     @ExperimentalAppActions
-    fun getDefaultParticipant(): Participant {
-        val p = Participant()
-        p.id = 123
-        p.name = "Gemini"
-        p.speakerIconUri = null
-        return p
+    fun generateParticipants(num: Int): List<Participant> {
+        val participants = ArrayList<Participant>()
+        for (i in 1..num) {
+            participants.add(Participant(i.toString(), "part-$i"))
+        }
+        return participants
     }
 
     @ExperimentalAppActions
-    fun printParticipants(participants: Set<Participant>, tag: String) {
+    fun getDefaultParticipant(): Participant {
+        return Participant("123", "Gemini")
+    }
+
+    @ExperimentalAppActions
+    fun getDefaultParticipantParcelable(): ParticipantParcelable {
+        return getDefaultParticipant().toParticipantParcelable()
+    }
+
+    @ExperimentalAppActions
+    fun printParticipants(participants: Collection<Participant>, tag: String) {
         Log.i(LOG_TAG, tag + ": printParticipants: set size=${participants.size}")
         for (v in participants) {
-            Log.i(LOG_TAG, "id=${v.id} name=${v.name}, uri=${v.speakerIconUri}")
+            Log.i(LOG_TAG, "\t $v")
         }
     }
 }
@@ -405,9 +418,9 @@
         scope.launch { raisedHandFlow.emit(Pair(callId, isHandRaised)) }
     }
 
-    override fun kickParticipantAction(callId: String?, participant: Participant?) {
+    override fun kickParticipantAction(callId: String?, participant: ParticipantParcelable?) {
         if (callId == null) return
-        scope.launch { kickParticipantFlow.emit(Pair(callId, participant)) }
+        scope.launch { kickParticipantFlow.emit(Pair(callId, participant?.toParticipant())) }
     }
 
     suspend fun waitForRaiseHandState(callId: String, expectedState: Boolean) {
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantActions.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantActions.aidl
index 945ebdc..0b7d068 100644
--- a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantActions.aidl
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantActions.aidl
@@ -16,7 +16,7 @@
 
 package androidx.core.telecom.extensions;
 
-import androidx.core.telecom.extensions.Participant;
+import androidx.core.telecom.extensions.ParticipantParcelable;
 import androidx.core.telecom.extensions.IActionsResultCallback;
 
 // ICS Client -> VOIP App
@@ -25,5 +25,5 @@
 oneway interface IParticipantActions {
     // V1
     void setHandRaised(in boolean handRaisedState, in IActionsResultCallback cb) = 0;
-    void kickParticipant(in Participant participant, in IActionsResultCallback cb) = 1;
+    void kickParticipant(in String participantId, in IActionsResultCallback cb) = 1;
 }
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantStateListener.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantStateListener.aidl
index f5f3ccf..bdfb511 100644
--- a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantStateListener.aidl
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/IParticipantStateListener.aidl
@@ -17,7 +17,7 @@
 package androidx.core.telecom.extensions;
 
 import java.util.List;
-import androidx.core.telecom.extensions.Participant;
+import androidx.core.telecom.extensions.ParticipantParcelable;
 import androidx.core.telecom.extensions.IParticipantActions;
 import androidx.core.telecom.extensions.IActionsResultCallback;
 
@@ -26,10 +26,10 @@
 @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface IParticipantStateListener {
     // V1 - Built-in states provided as part of handling basic participant support
-    void updateParticipants(in Participant[] participants) = 0;
-    void updateActiveParticipant(in int activeParticipant) = 1;
+    void updateParticipants(in ParticipantParcelable[] participants) = 0;
+    void updateActiveParticipant(in String activeParticipantId) = 1;
     // V1 - Updates for supported actions
-    void updateRaisedHandsAction(in int[] participants) = 2;
+    void updateRaisedHandsAction(in String[] participantIds) = 2;
     // Finish synchronization and start listening for actions updates
     void finishSync(in IParticipantActions cb) = 3;
 }
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Participant.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ParticipantParcelable.aidl
similarity index 81%
rename from core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Participant.aidl
rename to core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ParticipantParcelable.aidl
index 5a453e1..207291a 100644
--- a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Participant.aidl
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ParticipantParcelable.aidl
@@ -19,11 +19,9 @@
 @JavaDerive(equals = true, toString = true)
 @JavaPassthrough(annotation="@androidx.core.telecom.util.ExperimentalAppActions")
 @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
-parcelable Participant {
-    // ID of the participant (must be unique for each)
-    int id;
-    // Participant name
-    String name;
-    // Call icon associated with the participant
-    Uri speakerIconUri;
+parcelable ParticipantParcelable {
+    // ID of the participant (must be unique for each call and NOT reused)
+    String id;
+    // User visible Participant name
+    CharSequence name;
 }
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControl.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControl.aidl
index bbf7bf6..999fa0f 100644
--- a/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControl.aidl
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControl.aidl
@@ -1,7 +1,7 @@
 package androidx.core.telecom.test;
 
 import androidx.core.telecom.extensions.Capability;
-import androidx.core.telecom.extensions.Participant;
+import androidx.core.telecom.extensions.ParticipantParcelable;
 import androidx.core.telecom.test.ITestAppControlCallback;
 
 // NOTE: only supports one voip call at a time right now + suspend functions are not supported by
@@ -10,7 +10,7 @@
 interface ITestAppControl {
   void setCallback(in ITestAppControlCallback callback);
   String addCall(in List<Capability> capabilities, boolean isOutgoing);
-  void updateParticipants(in List<Participant> participants);
-  void updateActiveParticipant(in Participant participant);
-  void updateRaisedHands(in List<Participant> raisedHandsParticipants);
+  void updateParticipants(in List<ParticipantParcelable> participants);
+  void updateActiveParticipant(in ParticipantParcelable participant);
+  void updateRaisedHands(in List<ParticipantParcelable> raisedHandsParticipants);
 }
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControlCallback.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControlCallback.aidl
index 803c74ee..d9bc951 100644
--- a/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControlCallback.aidl
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/test/ITestAppControlCallback.aidl
@@ -1,9 +1,9 @@
 package androidx.core.telecom.test;
 
-import androidx.core.telecom.extensions.Participant;
+import androidx.core.telecom.extensions.ParticipantParcelable;
 
 @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface ITestAppControlCallback {
     void raiseHandStateAction(in String callId, boolean isHandRaised);
-    void kickParticipantAction(in String callId, in Participant participant);
+    void kickParticipantAction(in String callId, in ParticipantParcelable participant);
 }
\ No newline at end of file
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallAttributesCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallAttributesCompat.kt
index d53ddbc..865dcdd 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallAttributesCompat.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallAttributesCompat.kt
@@ -35,14 +35,18 @@
  * @param callType Information related to data being transmitted (voice, video, etc. )
  * @param callCapabilities Allows a package to opt into capabilities on the telecom side, on a
  *   per-call basis
+ * @param preferredStartingCallEndpoint allows clients to specify a [CallEndpointCompat] to start a
+ *   new call on. The [preferredStartingCallEndpoint] should be a value returned from
+ *   [CallsManager.getAvailableStartingCallEndpoints]. Once the call is started, Core-Telecom will
+ *   switch to the [preferredStartingCallEndpoint] before running the [CallControlScope].
  */
-public class CallAttributesCompat
-constructor(
+public class CallAttributesCompat(
     public val displayName: CharSequence,
     public val address: Uri,
     @Direction public val direction: Int,
     @CallType public val callType: Int = CALL_TYPE_AUDIO_CALL,
-    @CallCapability public val callCapabilities: Int = SUPPORTS_SET_INACTIVE
+    @CallCapability public val callCapabilities: Int = SUPPORTS_SET_INACTIVE,
+    public var preferredStartingCallEndpoint: CallEndpointCompat? = null
 ) {
     internal var mHandle: PhoneAccountHandle? = null
 
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
index 408ef98..c1ccf20 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
@@ -107,4 +107,9 @@
     ) : this(name, type) {
         mMackAddress = address
     }
+
+    /** Internal helper to determine if this [CallEndpointCompat] is EndpointType#TYPE_BLUETOOTH */
+    internal fun isBluetoothType(): Boolean {
+        return type == TYPE_BLUETOOTH
+    }
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
index 0cf8388..ee7d73b 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
@@ -18,6 +18,9 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.media.AudioDeviceCallback
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
 import android.os.Build.VERSION_CODES
 import android.os.Bundle
 import android.os.OutcomeReceiver
@@ -37,12 +40,17 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
 import androidx.core.telecom.CallAttributesCompat.Companion.CALL_TYPE_VIDEO_CALL
+import androidx.core.telecom.extensions.CallsManagerExtensions
 import androidx.core.telecom.extensions.ExtensionInitializationScope
+import androidx.core.telecom.extensions.ExtensionInitializationScopeImpl
 import androidx.core.telecom.internal.AddCallResult
 import androidx.core.telecom.internal.CallChannels
 import androidx.core.telecom.internal.CallSession
 import androidx.core.telecom.internal.CallSessionLegacy
 import androidx.core.telecom.internal.JetpackConnectionService
+import androidx.core.telecom.internal.PreCallEndpoints
+import androidx.core.telecom.internal.utils.AudioManagerUtil.Companion.getAvailableAudioDevices
+import androidx.core.telecom.internal.utils.EndpointUtils.Companion.getEndpointsFromAudioDeviceInfo
 import androidx.core.telecom.internal.utils.Utils
 import androidx.core.telecom.internal.utils.Utils.Companion.remapJetpackCapsToPlatformCaps
 import androidx.core.telecom.util.ExperimentalAppActions
@@ -54,8 +62,11 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withTimeout
@@ -73,7 +84,7 @@
  * descriptions.
  */
 @RequiresApi(VERSION_CODES.O)
-public class CallsManager public constructor(context: Context) {
+public class CallsManager(context: Context) : CallsManagerExtensions {
     private val mContext: Context = context
     private var mPhoneAccount: PhoneAccount? = null
     private val mTelecomManager: TelecomManager =
@@ -83,6 +94,9 @@
     // A single declared constant for a direct [Executor], since the coroutines primitives we invoke
     // from the associated callbacks will perform their own dispatch as needed.
     private val mDirectExecutor = Executor { it.run() }
+    // This list is modified in [getAvailableStartingCallEndpoints] and used to store the
+    // mappings of jetpack call endpoint UUIDs
+    private var mPreCallEndpointsList: MutableList<PreCallEndpoints> = mutableListOf()
 
     public companion object {
         @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -279,67 +293,40 @@
      * actions that go beyond the scope of a call, such as information about meeting participants
      * and icons.
      *
-     * Supported Extensions:
-     * - The ability to show meeting participants and information about those participants using
-     *   [ExtensionInitializationScope.addParticipantExtension]
-     *
-     * For example, using Participants as an example of extensions:
-     * ```
-     * scope.launch {
-     *         mCallsManager.addCallWithExtensions(attributes,
-     *             onAnswerLambda,
-     *             onDisconnectLambda,
-     *             onSetActiveLambda,
-     *             onSetInactiveLambda) {
-     *                 // Initialize extensions ...
-     *                 // Example: add participants support & associated actions
-     *                 val participantExtension = addParticipantExtension(initialParticipants)
-     *                 val raiseHandState = participantExtension.addRaiseHandSupport(
-     *                         initialRaisedHands) { onHandRaisedStateChanged ->
-     *                     // handle raised hand state changed
-     *                 }
-     *                 participantExtension.addKickParticipantSupport {
-     *                         participant ->
-     *                     // handle kicking the requested participant
-     *                 }
-     *                 // Call has been set up, perform in-call actions
-     *                 onCall {
-     *                     // Example: collect call state updates
-     *                     callStateFlow.onEach { newState ->
-     *                         // handle call state updates
-     *                     }.launchIn(this)
-     *                     // update participant extensions
-     *                     participantsFlow.onEach { newParticipants ->
-     *                         participantExtension.updateParticipants(newParticipants)
-     *                     }.launchIn(this)
-     *                     raisedHandsFlow.onEach { newRaisedHands ->
-     *                         raiseHandState.updateRaisedHands(newRaisedHands)
-     *                     }.launchIn(this)
-     *                 }
-     *             }
-     *         }
-     * }
-     * ```
-     *
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. )
+     * @param onAnswer where callType is the audio/video state the call should be answered as.
+     *   Telecom is informing your VoIP application to answer an incoming call and set it to active.
+     *   Telecom is requesting this on behalf of an system service (e.g. Automotive service) or a
+     *   device (e.g. Wearable).
+     * @param onDisconnect where disconnectCause represents the cause for disconnecting the call.
+     *   Telecom is informing your VoIP application to disconnect the incoming call. Telecom is
+     *   requesting this on behalf of an system service (e.g. Automotive service) or a device (e.g.
+     *   Wearable).
+     * @param onSetActive Telecom is informing your VoIP application to set the call active. Telecom
+     *   is requesting this on behalf of an system service (e.g. Automotive service) or a device
+     *   (e.g. Wearable).
+     * @param onSetInactive Telecom is informing your VoIP application to set the call inactive.
+     *   This is the same as holding a call for two endpoints but can be extended to setting a
+     *   meeting inactive. Telecom is requesting this on behalf of an system service (e.g.
+     *   Automotive service) or a device (e.g.Wearable). Note: Your app must stop using the
+     *   microphone and playing incoming media when returning.
      * @param init The scope used to first initialize Extensions that will be used when the call is
      *   first notified to the platform and UX surfaces. Once the call is set up, the user's
      *   implementation of [ExtensionInitializationScope.onCall] will be called.
-     * @see CallsManager.addCall
+     * @see CallsManagerExtensions.addCallWithExtensions
      */
-    // TODO: Refactor to Public API
-    @RequiresApi(VERSION_CODES.O)
     @ExperimentalAppActions
-    internal suspend fun addCallWithExtensions(
+    override suspend fun addCallWithExtensions(
         callAttributes: CallAttributesCompat,
         onAnswer: suspend (callType: @CallAttributesCompat.Companion.CallType Int) -> Unit,
         onDisconnect: suspend (disconnectCause: DisconnectCause) -> Unit,
         onSetActive: suspend () -> Unit,
         onSetInactive: suspend () -> Unit,
         init: suspend ExtensionInitializationScope.() -> Unit
-    ) = coroutineScope {
+    ): Unit = coroutineScope {
         Log.v(TAG, "addCall: begin")
         val eventFlow = MutableSharedFlow<CallEvent>()
-        val scope = ExtensionInitializationScope()
+        val scope = ExtensionInitializationScopeImpl()
         scope.init()
         val extensionJob = launch {
             Log.d(TAG, "addCall: connecting extensions")
@@ -359,10 +346,66 @@
         }
         // Ensure that when the call ends, we also cancel any ongoing coroutines/flows as part of
         // extension work
+        Log.d(TAG, "addCall: cancelling extension job")
         extensionJob.cancelAndJoin()
     }
 
     /**
+     * Fetch the current available call audio endpoints that can be used for a new call session. The
+     * callback flow will be continuously updated until the call session is established via
+     * [addCall]. Once [addCall] is invoked with a
+     * [CallAttributesCompat.preferredStartingCallEndpoint], the callback containing the
+     * [CallEndpointCompat] will be forced closed on behalf of the client. If the flow is canceled
+     * before adding the call, the [CallAttributesCompat.preferredStartingCallEndpoint] will be
+     * voided. If a call session isn't started, the flow should be cleaned up client-side by calling
+     * cancel() from the same [kotlinx.coroutines.CoroutineScope] the [callbackFlow] is collecting
+     * in.
+     *
+     * @return a flow of [CallEndpointCompat]s that can be used for a new call session
+     */
+    public fun getAvailableStartingCallEndpoints(): Flow<List<CallEndpointCompat>> = callbackFlow {
+        val audioManager = mContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+        // [AudioDeviceInfo] <-- AudioManager / platform
+        val initialAudioDevices = getAvailableAudioDevices(audioManager)
+        // [AudioDeviceInfo] --> [CallEndpoints]
+        val initialEndpoints = getEndpointsFromAudioDeviceInfo(mContext, initialAudioDevices)
+
+        val preCallEndpoints = PreCallEndpoints(initialEndpoints.toMutableList(), this.channel)
+        mPreCallEndpointsList.add(preCallEndpoints)
+
+        val audioDeviceCallback =
+            object : AudioDeviceCallback() {
+                override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
+                    if (addedDevices != null) {
+                        preCallEndpoints.endpointsAddedUpdate(
+                            getEndpointsFromAudioDeviceInfo(mContext, addedDevices.toList())
+                        )
+                    }
+                }
+
+                override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
+                    if (removedDevices != null) {
+                        preCallEndpoints.endpointsRemovedUpdate(
+                            getEndpointsFromAudioDeviceInfo(mContext, removedDevices.toList())
+                        )
+                    }
+                }
+            }
+        // The following callback is needed in the event the user connects or disconnects
+        // and audio device after this API is called.
+        audioManager.registerAudioDeviceCallback(audioDeviceCallback, null /*handler*/)
+        // Send the initial list of pre-call [CallEndpointCompat]s out to the client. They
+        // will be emitted and cached in the Flow & only consumed once the client has
+        // collected it.
+        trySend(initialEndpoints)
+        awaitClose {
+            Log.i(TAG, "getAvailableStartingCallEndpoints: awaitClose")
+            audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
+            mPreCallEndpointsList.remove(preCallEndpoints)
+        }
+    }
+
+    /**
      * Internal version of addCall, which also allows components in the library to consume generic
      * events generated from the remote InCallServices. This facilitates the creation of Jetpack
      * defined extensions.
@@ -392,6 +435,11 @@
         // exception, addCall will unblock.
         val blockingSessionExecution = CompletableDeferred<Unit>(parent = coroutineContext.job)
 
+        val preCallEndpoints: PreCallEndpoints? =
+            mPreCallEndpointsList.find {
+                it.isCallEndpointBeingTracked(callAttributes.preferredStartingCallEndpoint)
+            }
+
         // create a call session based off the build version
         @RequiresApi(34)
         if (Utils.hasPlatformV2Apis()) {
@@ -408,6 +456,7 @@
                     onDisconnect,
                     onSetActive,
                     onSetInactive,
+                    preCallEndpoints,
                     callChannels,
                     onEvent,
                     blockingSessionExecution
@@ -442,8 +491,6 @@
 
             pauseExecutionUntilCallIsReadyOrTimeout(openResult, blockingSessionExecution)
 
-            callSession.maybeSwitchToSpeakerOnCallStart()
-
             /* at this point in time we have CallControl object */
             val scope =
                 CallSession.CallControlScopeImpl(
@@ -453,6 +500,8 @@
                     coroutineContext
                 )
 
+            callSession.maybeSwitchStartingEndpoint(callAttributes.preferredStartingCallEndpoint)
+
             // Run the clients code with the session active and exposed via the CallControlScope
             // interface implementation declared above.
             scope.block()
@@ -473,6 +522,8 @@
                     onSetActive,
                     onSetInactive,
                     onEvent,
+                    callAttributes.preferredStartingCallEndpoint,
+                    preCallEndpoints,
                     blockingSessionExecution
                 )
 
@@ -493,6 +544,7 @@
             // CallControlScope interface implementation declared above.
             scope.block()
         }
+        preCallEndpoints?.mSendChannel?.close()
         blockingSessionExecution.await()
     }
 
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/InCallServiceCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/InCallServiceCompat.kt
index 835d31e..e9a14fe 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/InCallServiceCompat.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/InCallServiceCompat.kt
@@ -24,7 +24,9 @@
 import android.util.Log
 import androidx.annotation.CallSuper
 import androidx.annotation.RequiresApi
-import androidx.core.telecom.extensions.CallExtensionsScope
+import androidx.core.telecom.extensions.CallExtensionScope
+import androidx.core.telecom.extensions.CallExtensionScopeImpl
+import androidx.core.telecom.extensions.CallExtensions
 import androidx.core.telecom.util.ExperimentalAppActions
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
@@ -37,13 +39,14 @@
  * [LifecycleOwner]
  */
 @RequiresApi(Build.VERSION_CODES.O)
-internal open class InCallServiceCompat : InCallService(), LifecycleOwner {
+@Suppress("ContextNameSuffix")
+public open class InCallServiceCompat : InCallService(), LifecycleOwner, CallExtensions {
     // Since we define this service as a LifecycleOwner, we need to implement this dispatcher as
     // well. See [LifecycleService] for the example used to implement [LifecycleOwner].
     private val dispatcher = ServiceLifecycleDispatcher(this)
 
-    companion object {
-        private val TAG = InCallServiceCompat::class.simpleName
+    private companion object {
+        private val TAG = InCallService::class.simpleName
     }
 
     override val lifecycle: Lifecycle
@@ -56,7 +59,8 @@
     }
 
     @CallSuper
-    override fun onBind(intent: Intent): IBinder? {
+    @Suppress("InvalidNullabilityOverride")
+    override fun onBind(intent: Intent?): IBinder? {
         dispatcher.onServicePreSuperOnBind()
         return super.onBind(intent)
     }
@@ -81,7 +85,6 @@
     @CallSuper
     override fun onDestroy() {
         dispatcher.onServicePreSuperOnDestroy()
-        // Todo: invoke CapabilityExchangeListener#onRemoveExtensions to inform the VOIP app
         super.onDestroy()
     }
 
@@ -89,42 +92,16 @@
      * Connects extensions to the provided [Call], allowing the call to support additional optional
      * behaviors beyond the traditional call state management.
      *
-     * The following extension is supported on a call:
-     * - [CallExtensionsScope.addParticipantExtension] - Adds the ability to represent the
-     *   participants in the call.
-     *
-     * For example, an extension may allow the participants of a meeting to be surfaced to this
-     * application so that the user can view and manage the participants in the meeting on different
-     * surfaces:
-     * ```
-     * class InCallServiceImpl : InCallServiceCompat() {
-     * ...
-     *   override fun onCallAdded(call: Call) {
-     *     lifecycleScope.launch {
-     *       connectExtensions(context, call) {
-     *         // Initialize extensions
-     *         onConnected { call ->
-     *           // change call states & listen/update extensions
-     *         }
-     *       }
-     *       // Once the call is destroyed, control flow will resume again
-     *     }
-     *   }
-     *  ...
-     * }
-     * ```
-     *
      * @param call The Call to connect extensions on.
      * @param init The scope used to initialize and manage extensions in the scope of the Call.
+     * @see CallExtensions.connectExtensions
      */
-    // TODO: Refactor to Public API
     @ExperimentalAppActions
-    @RequiresApi(Build.VERSION_CODES.O)
-    suspend fun connectExtensions(call: Call, init: CallExtensionsScope.() -> Unit) {
+    override suspend fun connectExtensions(call: Call, init: CallExtensionScope.() -> Unit) {
         // Attach this to the scope of the InCallService so it does not outlive its lifecycle
         lifecycleScope
             .launch {
-                val scope = CallExtensionsScope(applicationContext, this, call)
+                val scope = CallExtensionScopeImpl(applicationContext, this, call)
                 Log.v(TAG, "connectExtensions: calling init")
                 scope.init()
                 Log.v(TAG, "connectExtensions: connecting extensions")
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ActionsResultCallback.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ActionsResultCallback.kt
index 1d5f6df..2f088c4 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ActionsResultCallback.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ActionsResultCallback.kt
@@ -53,10 +53,13 @@
                     )
                 ) {
                     Log.i(TAG, "waitForResponse: VoIP app returned a result")
+                } else {
+                    Log.i(TAG, "waitForResponse: latch timeout reached")
+                    result = CallControlResult.Error(CallException.ERROR_OPERATION_TIMED_OUT)
                 }
             }
         } catch (e: TimeoutCancellationException) {
-            Log.i(TAG, "waitForResponse: timeout reached")
+            Log.i(TAG, "waitForResponse: coroutine timeout reached")
             result = CallControlResult.Error(CallException.ERROR_OPERATION_TIMED_OUT)
         }
         return result
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScope.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScope.kt
new file mode 100644
index 0000000..349ab3f
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScope.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.telecom.Call
+import androidx.core.telecom.util.ExperimentalAppActions
+
+/**
+ * Provides a scope where extensions can be first initialized and next managed for a [Call] once
+ * [onConnected] is called.
+ *
+ * The following extension is supported on a call:
+ * - [addParticipantExtension] - Show the user more information about the [Participant]s in the
+ *   call.
+ *
+ * ```
+ * class InCallServiceImpl : InCallServiceCompat() {
+ * ...
+ *   override fun onCallAdded(call: Call) {
+ *     lifecycleScope.launch {
+ *       connectExtensions(context, call) {
+ *         // Initialize extensions
+ *         onConnected { call ->
+ *           // change call states & listen for extension updates/send extension actions
+ *         }
+ *       }
+ *       // Once the call is destroyed, control flow will resume again
+ *     }
+ *   }
+ *  ...
+ * }
+ * ```
+ */
+@ExperimentalAppActions
+public interface CallExtensionScope {
+
+    /**
+     * Called when the [Call] extensions have been successfully set up and are ready to be used.
+     *
+     * @param block Called when the [Call] and initialized extensions are ready to be used.
+     */
+    public fun onConnected(block: suspend (Call) -> Unit)
+
+    /**
+     * Add support for this remote surface to display information related to the [Participant]s in
+     * this call.
+     *
+     * ```
+     * connectExtensions(call) {
+     *     val participantExtension = addParticipantExtension(
+     *         // consume participant changed events
+     *     )
+     *     onConnected {
+     *         // extensions have been negotiated and actions are ready to be used
+     *     }
+     * }
+     * ```
+     *
+     * @param onActiveParticipantChanged Called with the active [Participant] in the call has
+     *   changed. If this method is called with a `null` [Participant], there is no active
+     *   [Participant]. The active [Participant] in the call is the [Participant] that should take
+     *   focus and be either more prominent on the screen or otherwise featured as active in UI. For
+     *   example, this could be the [Participant] that is actively talking or presenting.
+     * @param onParticipantsUpdated Called when the [Participant]s in the [Call] have changed and
+     *   the UI should be updated.
+     * @return The interface that is used to set up additional actions for this extension.
+     */
+    public fun addParticipantExtension(
+        onActiveParticipantChanged: suspend (Participant?) -> Unit,
+        onParticipantsUpdated: suspend (Set<Participant>) -> Unit
+    ): ParticipantExtensionRemote
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionsScope.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScopeImpl.kt
similarity index 92%
rename from core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionsScope.kt
rename to core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScopeImpl.kt
index 8f24c80..7b44167 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionsScope.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensionScopeImpl.kt
@@ -57,7 +57,7 @@
  * [onExchangeComplete], which is called when capability exchange has completed and the extension
  * should be initialized.
  */
-@ExperimentalAppActions
+@OptIn(ExperimentalAppActions::class)
 internal data class CallExtensionCreator(
     val extensionCapability: Capability,
     val onExchangeComplete: suspend (Capability?, CapabilityExchangeListenerRemote?) -> Unit
@@ -68,7 +68,7 @@
  * Contains the capabilities that the VOIP app supports and the remote binder implementation used to
  * communicate with the remote process.
  */
-@ExperimentalAppActions
+@OptIn(ExperimentalAppActions::class)
 private data class CapabilityExchangeResult(
     val voipCapabilities: Set<Capability>,
     val extensionInitializationBinder: CapabilityExchangeListenerRemote
@@ -89,15 +89,13 @@
  * }
  * ```
  */
-// TODO: Refactor to Public API
+@OptIn(ExperimentalAppActions::class)
 @RequiresApi(Build.VERSION_CODES.O)
-@ExperimentalAppActions
-internal class CallExtensionsScope(
+internal class CallExtensionScopeImpl(
     private val applicationContext: Context,
     private val callScope: CoroutineScope,
     private val call: Call
-) {
-
+) : CallExtensionScope {
     companion object {
         internal const val TAG = "CallExtensions"
 
@@ -126,46 +124,26 @@
     // need to query the Capability after CallExtensionScope initialization has completed.
     private val callExtensionCreators = HashSet<() -> CallExtensionCreator>()
 
-    /**
-     * Called when the [Call] extensions have been successfully set up and are ready to be used.
-     *
-     * @param block Called when extensions are ready to be used
-     */
-    fun onConnected(block: suspend (Call) -> Unit) {
+    override fun onConnected(block: suspend (Call) -> Unit) {
         delegate = block
     }
 
-    /**
-     * Add support for representing Participants in this call.
-     *
-     * ```
-     * connectExtensions(call) {
-     *     val participantExtension = addParticipantExtension(
-     *         // consume participant changed events
-     *     )
-     *     onConnected {
-     *         // extensions have been negotiated and actions are ready to be used
-     *     }
-     * }
-     * ```
-     *
-     * @param onActiveParticipantChanged Called with the new active Participant any time it changes.
-     *   If this method is called with `null`, there is no active Participant.
-     * @param onParticipantsUpdated Called when the Participants in the call have changed.
-     * @return The extension connection that should be used to set up additional actions.
-     */
-    fun addParticipantExtension(
+    override fun addParticipantExtension(
         onActiveParticipantChanged: suspend (Participant?) -> Unit,
         onParticipantsUpdated: suspend (Set<Participant>) -> Unit
-    ): ParticipantExtensionRemote {
+    ): ParticipantExtensionRemoteImpl {
         val extension =
-            ParticipantExtensionRemote(callScope, onActiveParticipantChanged, onParticipantsUpdated)
+            ParticipantExtensionRemoteImpl(
+                callScope,
+                onActiveParticipantChanged,
+                onParticipantsUpdated
+            )
         registerExtension {
             CallExtensionCreator(
                 extensionCapability =
                     Capability().apply {
                         featureId = Extensions.PARTICIPANT
-                        featureVersion = ParticipantExtension.VERSION
+                        featureVersion = ParticipantExtensionImpl.VERSION
                         supportedActions = extension.actions
                     },
                 onExchangeComplete = extension::onExchangeComplete
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensions.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensions.kt
new file mode 100644
index 0000000..accbce2
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallExtensions.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build.VERSION_CODES
+import android.telecom.Call
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.util.ExperimentalAppActions
+
+/**
+ * Provides the capability for a remote surface (automotive, watch, etc...) to connect to extensions
+ * provided by calling applications.
+ *
+ * Extensions allow a calling application to support additional optional features beyond the Android
+ * platform provided features defined in [Call]. When a new [Call] has been created, this interface
+ * allows the remote surface to also define which extensions that it supports in its UI. If the
+ * calling application providing the [Call] also supports the extension, the extension will be
+ * marked as supported. At that point, the remote surface can receive state updates and send action
+ * requests to the calling application to change state.
+ */
+public interface CallExtensions {
+    /**
+     * Connects extensions to the provided [call], allowing the [call] to support additional
+     * optional behaviors beyond the traditional call state management provided by [Call].
+     *
+     * @param call The [Call] to connect extensions on.
+     * @param init The scope used to initialize and manage extensions in the scope of the [Call].
+     * @see CallExtensionScope
+     */
+    @RequiresApi(VERSION_CODES.O)
+    @ExperimentalAppActions
+    public suspend fun connectExtensions(call: Call, init: CallExtensionScope.() -> Unit)
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt
new file mode 100644
index 0000000..2cccd8a
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build.VERSION_CODES
+import android.telecom.DisconnectCause
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.util.ExperimentalAppActions
+
+/**
+ * Provide the ability for [CallsManager] to support extensions on a call.
+ *
+ * Extensions allow an application to support optional features beyond the scope of call state
+ * management and audio routing. These optional features provide the application with the ability to
+ * describe additional information about the call, which allows remote surfaces (automotive,
+ * watches, etc..) to provide UX related to this additional information. Additionally, remote
+ * surfaces can perform actions using a configured extension to notify this application of a remote
+ * user request.
+ *
+ * @see ExtensionInitializationScope
+ */
+public interface CallsManagerExtensions {
+    /**
+     * Adds a call with extensions support using [ExtensionInitializationScope], which allows an app
+     * to implement optional additional actions that go beyond the scope of a call, such as
+     * information about meeting participants and icons.
+     *
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. )
+     * @param onAnswer where callType is the audio/video state the call should be answered as.
+     *   Telecom is informing your VoIP application to answer an incoming call and set it to active.
+     *   Telecom is requesting this on behalf of an system service (e.g. Automotive service) or a
+     *   device (e.g. Wearable).
+     * @param onDisconnect where disconnectCause represents the cause for disconnecting the call.
+     *   Telecom is informing your VoIP application to disconnect the incoming call. Telecom is
+     *   requesting this on behalf of an system service (e.g. Automotive service) or a device (e.g.
+     *   Wearable).
+     * @param onSetActive Telecom is informing your VoIP application to set the call active. Telecom
+     *   is requesting this on behalf of an system service (e.g. Automotive service) or a device
+     *   (e.g. Wearable).
+     * @param onSetInactive Telecom is informing your VoIP application to set the call inactive.
+     *   This is the same as holding a call for two endpoints but can be extended to setting a
+     *   meeting inactive. Telecom is requesting this on behalf of an system service (e.g.
+     *   Automotive service) or a device (e.g.Wearable). Note: Your app must stop using the
+     *   microphone and playing incoming media when returning.
+     * @param init The scope used to first initialize Extensions that will be used when the call is
+     *   first notified to the platform and UX surfaces. Once the call is set up, the user's
+     *   implementation of [ExtensionInitializationScope.onCall] will be called.
+     * @see CallsManager.addCall
+     */
+    @RequiresApi(VERSION_CODES.O)
+    @ExperimentalAppActions
+    public suspend fun addCallWithExtensions(
+        callAttributes: CallAttributesCompat,
+        onAnswer: suspend (callType: @CallAttributesCompat.Companion.CallType Int) -> Unit,
+        onDisconnect: suspend (disconnectCause: DisconnectCause) -> Unit,
+        onSetActive: suspend () -> Unit,
+        onSetInactive: suspend () -> Unit,
+        init: suspend ExtensionInitializationScope.() -> Unit
+    )
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
index dd5813d..63c9e5b 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
@@ -16,39 +16,62 @@
 
 package androidx.core.telecom.extensions
 
-import android.os.Build
-import android.os.Bundle
-import android.os.RemoteException
-import android.util.Log
-import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallControlScope
-import androidx.core.telecom.CallsManager
-import androidx.core.telecom.internal.CapabilityExchangeRemote
-import androidx.core.telecom.internal.CapabilityExchangeRepository
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.launch
 
 /**
- * The scope used to initialize extensions that will be used during the call and manage extensions
- * during the call.
+ * The scope used to initialize extensions on a call as well as manage initialized extensions
+ * associated with the call once the call has been set up.
  *
- * Extensions should first be initialized in this scope. Once the call is set up, the user provided
- * implementation of [onCall] will be run, which should manage the call and extension states during
- * the lifetime of when the call is active.
+ * Extensions contain state and optional actions that are used to support additional features on a
+ * call, such as information about the participants in the call.
+ *
+ * Supported Extensions:
+ * - The ability to describe meeting participant information as well as actions on those
+ *   participants using [addParticipantExtension]
+ *
+ * For example, to add participant support, the participant extension can be created during
+ * initialization and then used as part of [onCall] to update participant state and listen to action
+ * requests from remote surfaces:
+ * ```
+ * scope.launch {
+ *         mCallsManager.addCallWithExtensions(attributes,
+ *             onAnswerLambda,
+ *             onDisconnectLambda,
+ *             onSetActiveLambda,
+ *             onSetInactiveLambda) {
+ *                 // Initialize extensions ...
+ *                 // Example: add participants support & associated actions
+ *                 val participantExtension = addParticipantExtension(initialParticipants)
+ *                 val raiseHandState = participantExtension.addRaiseHandSupport(
+ *                         initialRaisedHands) { onHandRaisedStateChanged ->
+ *                     // handle raised hand state changed
+ *                 }
+ *                 participantExtension.addKickParticipantSupport {
+ *                         participant ->
+ *                     // handle kicking the requested participant
+ *                 }
+ *                 // Call has been set up, perform in-call actions
+ *                 onCall {
+ *                     // Example: collect call state updates
+ *                     callStateFlow.onEach { newState ->
+ *                         // handle call state updates
+ *                     }.launchIn(this)
+ *                     // update participant extensions
+ *                     participantsFlow.onEach { newParticipants ->
+ *                         participantExtension.updateParticipants(newParticipants)
+ *                     }.launchIn(this)
+ *                     raisedHandsFlow.onEach { newRaisedHands ->
+ *                         raiseHandState.updateRaisedHands(newRaisedHands)
+ *                     }.launchIn(this)
+ *                 }
+ *             }
+ *         }
+ * }
+ * ```
  */
-// TODO: Refactor to Public API
-@RequiresApi(Build.VERSION_CODES.O)
 @ExperimentalAppActions
-internal class ExtensionInitializationScope {
-    private companion object {
-        const val LOG_TAG = Extensions.LOG_TAG + "(EIS)"
-    }
-
-    private var onCreateDelegate: (suspend CallControlScope.() -> Unit)? = null
-    private val extensionCreators = HashSet<(CapabilityExchangeRepository) -> Capability>()
+public interface ExtensionInitializationScope {
 
     /**
      * User provided callback implementation that is run when the call is ready using the provided
@@ -57,130 +80,21 @@
      * @param onCall callback invoked when the call has been notified to the framework and the call
      *   is ready
      */
-    fun onCall(onCall: suspend CallControlScope.() -> Unit) {
-        Log.v(LOG_TAG, "onCall: storing delegate")
-        // Capture onCall impl
-        onCreateDelegate = onCall
-    }
+    public fun onCall(onCall: suspend CallControlScope.() -> Unit)
 
     /**
-     * Adds the participant extension to a call, which provides the ability to specify participant
-     * related information.
+     * Adds the participant extension to a call, which provides the ability for this application to
+     * specify participant related information, which will be shared with remote surfaces that
+     * support displaying that information (automotive, watch, etc...).
      *
-     * @param initialParticipants The initial participants in the call
-     * @param initialActiveParticipant The initial participant that is active in the call
-     * @return The interface used to update the participant state to remote InCallServices
+     * @param initialParticipants The initial [Set] of [Participant]s in the call
+     * @param initialActiveParticipant The initial [Participant] that is active in the call or
+     *   `null` if there is no active participant.
+     * @return The interface used by this application to further update the participant extension
+     *   state to remote surfaces
      */
-    // TODO: Refactor to Public API
-    fun addParticipantExtension(
+    public fun addParticipantExtension(
         initialParticipants: Set<Participant> = emptySet(),
         initialActiveParticipant: Participant? = null
-    ): ParticipantExtension {
-        val participant = ParticipantExtension(initialParticipants, initialActiveParticipant)
-        registerExtension(onExchangeStarted = participant::onExchangeStarted)
-        return participant
-    }
-
-    /**
-     * Register an extension to be created once capability exchange begins.
-     *
-     * @param onExchangeStarted The capability exchange procedure has begun and the extension needs
-     *   to register the callbacks it will be handling as well as return the [Capability] of the
-     *   extension, which will be used during capability exchange.
-     */
-    private fun registerExtension(onExchangeStarted: (CapabilityExchangeRepository) -> Capability) {
-        extensionCreators.add(onExchangeStarted)
-    }
-
-    /**
-     * Collects [CallsManager.CallEvent]s that were received from connected InCallServices on the
-     * provided CoroutineScope and optionally consumes the events. If we recognize and consume a
-     * [CallsManager.CallEvent], this will create a Coroutine as a child of the [CoroutineScope]
-     * provided here to manage the lifecycle of the task.
-     *
-     * @param scope The CoroutineScope that will be launched to perform the collection of events
-     * @param eventFlow The [SharedFlow] representing the incoming [CallsManager.CallEvent]s from
-     *   the framework.
-     */
-    internal fun collectEvents(
-        scope: CoroutineScope,
-        eventFlow: SharedFlow<CallsManager.CallEvent>
-    ) {
-        scope.launch {
-            Log.i(LOG_TAG, "collectEvents: starting collection")
-            eventFlow
-                .onCompletion { Log.i(LOG_TAG, "collectEvents: finishing...") }
-                .collect {
-                    Log.v(LOG_TAG, "collectEvents: received ${it.event}")
-                    onEvent(it)
-                }
-        }
-    }
-
-    /**
-     * Invokes the user provided implementation of [CallControlScope] when the call is ready.
-     *
-     * @param scope The enclosing [CallControlScope] passed in by [CallsManager.addCall] to be used
-     *   to call [onCall].
-     */
-    internal fun invokeDelegate(scope: CallControlScope) {
-        scope.launch {
-            Log.i(LOG_TAG, "invokeDelegate")
-            onCreateDelegate?.invoke(scope)
-        }
-    }
-
-    /**
-     * Consumes [CallsManager.CallEvent]s received from remote InCallService implementations.
-     *
-     * Provides a [CoroutineScope] for events to use to handle the event and set up a session for
-     * the lifecycle of the call.
-     *
-     * @param callEvent The event that we received from an InCallService.
-     */
-    private fun CoroutineScope.onEvent(callEvent: CallsManager.CallEvent) {
-        when (callEvent.event) {
-            Extensions.EVENT_JETPACK_CAPABILITY_EXCHANGE -> {
-                handleCapabilityExchangeEvent(callEvent.extras)
-            }
-        }
-    }
-
-    /**
-     * Starts a Coroutine to handle CapabilityExchange and extensions for the lifecycle of the call.
-     *
-     * @param extras The extras included as part of the Capability Exchange event.
-     */
-    private fun CoroutineScope.handleCapabilityExchangeEvent(extras: Bundle) {
-        val version = extras.getInt(Extensions.EXTRA_CAPABILITY_EXCHANGE_VERSION)
-        val capExchange =
-            ICapabilityExchange.Stub.asInterface(
-                    extras.getBinder(Extensions.EXTRA_CAPABILITY_EXCHANGE_BINDER)
-                )
-                ?.let { CapabilityExchangeRemote(it) }
-        if (capExchange == null) {
-            Log.w(
-                LOG_TAG,
-                "handleCapabilityExchangeEvent: capExchange binder is null, can" +
-                    " not complete cap exchange"
-            )
-            return
-        }
-        Log.i(LOG_TAG, "handleCapabilityExchangeEvent: received CE request, v=#$version")
-        // Create a child scope for setting up and running the extensions so that we can cancel
-        // the child scope when the remote ICS disconnects without affecting the parent scope.
-        val connectionScope = CoroutineScope(coroutineContext)
-        // Create a new repository for each new connection
-        val callbackRepository = CapabilityExchangeRepository(connectionScope)
-        val capabilities = extensionCreators.map { it.invoke(callbackRepository) }
-
-        Log.i(LOG_TAG, "handleCapabilityExchangeEvent: beginning exchange, caps=$capabilities")
-        try {
-            capExchange.beginExchange(capabilities, callbackRepository.listener)
-        } catch (e: RemoteException) {
-            Log.w(LOG_TAG, "handleCapabilityExchangeEvent: Remote could not be reached: $e")
-            // This will cancel the surrounding coroutineScope
-            throw e
-        }
-    }
+    ): ParticipantExtension
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt
new file mode 100644
index 0000000..ed3156e4
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build
+import android.os.Bundle
+import android.os.RemoteException
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlScope
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.internal.CapabilityExchangeRemote
+import androidx.core.telecom.internal.CapabilityExchangeRepository
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.launch
+
+/**
+ * The scope used to initialize extensions that will be used during the call and manage extensions
+ * during the call.
+ *
+ * Extensions should first be initialized in this scope. Once the call is set up, the user provided
+ * implementation of [onCall] will be run, which should manage the call and extension states during
+ * the lifetime of when the call is active.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalAppActions::class)
+internal class ExtensionInitializationScopeImpl : ExtensionInitializationScope {
+    private companion object {
+        const val LOG_TAG = Extensions.LOG_TAG + "(EIS)"
+    }
+
+    private var onCreateDelegate: (suspend CallControlScope.() -> Unit)? = null
+    private val extensionCreators = HashSet<(CapabilityExchangeRepository) -> Capability>()
+
+    override fun onCall(onCall: suspend CallControlScope.() -> Unit) {
+        Log.v(LOG_TAG, "onCall: storing delegate")
+        // Capture onCall impl
+        onCreateDelegate = onCall
+    }
+
+    override fun addParticipantExtension(
+        initialParticipants: Set<Participant>,
+        initialActiveParticipant: Participant?
+    ): ParticipantExtension {
+        val participant = ParticipantExtensionImpl(initialParticipants, initialActiveParticipant)
+        registerExtension(onExchangeStarted = participant::onExchangeStarted)
+        return participant
+    }
+
+    /**
+     * Register an extension to be created once capability exchange begins.
+     *
+     * @param onExchangeStarted The capability exchange procedure has begun and the extension needs
+     *   to register the callbacks it will be handling as well as return the [Capability] of the
+     *   extension, which will be used during capability exchange.
+     */
+    private fun registerExtension(onExchangeStarted: (CapabilityExchangeRepository) -> Capability) {
+        extensionCreators.add(onExchangeStarted)
+    }
+
+    /**
+     * Collects [CallsManager.CallEvent]s that were received from connected InCallServices on the
+     * provided CoroutineScope and optionally consumes the events. If we recognize and consume a
+     * [CallsManager.CallEvent], this will create a Coroutine as a child of the [CoroutineScope]
+     * provided here to manage the lifecycle of the task.
+     *
+     * @param scope The CoroutineScope that will be launched to perform the collection of events
+     * @param eventFlow The [SharedFlow] representing the incoming [CallsManager.CallEvent]s from
+     *   the framework.
+     */
+    internal fun collectEvents(
+        scope: CoroutineScope,
+        eventFlow: SharedFlow<CallsManager.CallEvent>
+    ) {
+        scope.launch {
+            Log.i(LOG_TAG, "collectEvents: starting collection")
+            eventFlow
+                .onCompletion { Log.i(LOG_TAG, "collectEvents: finishing...") }
+                .collect {
+                    Log.v(LOG_TAG, "collectEvents: received ${it.event}")
+                    onEvent(it)
+                }
+        }
+    }
+
+    /**
+     * Invokes the user provided implementation of [CallControlScope] when the call is ready.
+     *
+     * @param scope The enclosing [CallControlScope] passed in by [CallsManager.addCall] to be used
+     *   to call [onCall].
+     */
+    internal fun invokeDelegate(scope: CallControlScope) {
+        scope.launch {
+            Log.i(LOG_TAG, "invokeDelegate")
+            onCreateDelegate?.invoke(scope)
+        }
+    }
+
+    /**
+     * Consumes [CallsManager.CallEvent]s received from remote InCallService implementations.
+     *
+     * Provides a [CoroutineScope] for events to use to handle the event and set up a session for
+     * the lifecycle of the call.
+     *
+     * @param callEvent The event that we received from an InCallService.
+     */
+    private fun CoroutineScope.onEvent(callEvent: CallsManager.CallEvent) {
+        when (callEvent.event) {
+            Extensions.EVENT_JETPACK_CAPABILITY_EXCHANGE -> {
+                handleCapabilityExchangeEvent(callEvent.extras)
+            }
+        }
+    }
+
+    /**
+     * Starts a Coroutine to handle CapabilityExchange and extensions for the lifecycle of the call.
+     *
+     * @param extras The extras included as part of the Capability Exchange event.
+     */
+    private fun CoroutineScope.handleCapabilityExchangeEvent(extras: Bundle) {
+        val version = extras.getInt(Extensions.EXTRA_CAPABILITY_EXCHANGE_VERSION)
+        val capExchange =
+            ICapabilityExchange.Stub.asInterface(
+                    extras.getBinder(Extensions.EXTRA_CAPABILITY_EXCHANGE_BINDER)
+                )
+                ?.let { CapabilityExchangeRemote(it) }
+        if (capExchange == null) {
+            Log.w(
+                LOG_TAG,
+                "handleCapabilityExchangeEvent: capExchange binder is null, can" +
+                    " not complete cap exchange"
+            )
+            return
+        }
+        Log.i(LOG_TAG, "handleCapabilityExchangeEvent: received CE request, v=#$version")
+        // Create a child scope for setting up and running the extensions so that we can cancel
+        // the child scope when the remote ICS disconnects without affecting the parent scope.
+        val connectionScope = CoroutineScope(coroutineContext)
+        // Create a new repository for each new connection
+        val callbackRepository = CapabilityExchangeRepository(connectionScope)
+        val capabilities = extensionCreators.map { it.invoke(callbackRepository) }
+
+        Log.i(LOG_TAG, "handleCapabilityExchangeEvent: beginning exchange, caps=$capabilities")
+        try {
+            capExchange.beginExchange(capabilities, callbackRepository.listener)
+        } catch (e: RemoteException) {
+            Log.w(LOG_TAG, "handleCapabilityExchangeEvent: Remote could not be reached: $e")
+            // This will cancel the surrounding coroutineScope
+            throw e
+        }
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Extensions.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Extensions.kt
index 90bbd27..f58ce43 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Extensions.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Extensions.kt
@@ -16,44 +16,28 @@
 
 package androidx.core.telecom.extensions
 
-import androidx.annotation.IntDef
-import androidx.annotation.RestrictTo
+/** Internal constants related to Extensions that do not need to be exposed as a public API. */
+internal object Extensions {
+    internal const val LOG_TAG = "CallsManagerE"
 
-@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)
-// TODO: move addCallWithExtensions
-public interface Extensions {
-    public companion object {
-        internal const val LOG_TAG = "CallsManagerE"
+    /**
+     * EVENT used by InCallService as part of sendCallEvent to notify the VOIP Application that this
+     * InCallService supports jetpack extensions
+     */
+    internal const val EVENT_JETPACK_CAPABILITY_EXCHANGE =
+        "android.telecom.event.CAPABILITY_EXCHANGE"
 
-        /**
-         * EVENT used by InCallService as part of sendCallEvent to notify the VOIP Application that
-         * this InCallService supports jetpack extensions
-         */
-        internal const val EVENT_JETPACK_CAPABILITY_EXCHANGE =
-            "android.telecom.event.CAPABILITY_EXCHANGE"
+    /** VERSION used for handling future compatibility in capability exchange. */
+    internal const val EXTRA_CAPABILITY_EXCHANGE_VERSION =
+        "androidx.core.telecom.extensions.extra.CAPABILITY_EXCHANGE_VERSION"
 
-        /** VERSION used for handling future compatibility in capability exchange. */
-        internal const val EXTRA_CAPABILITY_EXCHANGE_VERSION = "CAPABILITY_EXCHANGE_VERSION"
+    /**
+     * BINDER used for handling capability exchange between the ICS and VOIP app sides, sent as part
+     * of sendCallEvent in the included extras.
+     */
+    internal const val EXTRA_CAPABILITY_EXCHANGE_BINDER =
+        "androidx.core.telecom.extensions.extra.CAPABILITY_EXCHANGE_BINDER"
 
-        /**
-         * BINDER used for handling capability exchange between the ICS and VOIP app sides, sent as
-         * part of sendCallEvent in the included extras.
-         */
-        internal const val EXTRA_CAPABILITY_EXCHANGE_BINDER = "CAPABILITY_EXCHANGE_BINDER"
-
-        /**
-         * Constants used to denote the type of Extension supported by the [Capability] being
-         * registered.
-         */
-        @Target(AnnotationTarget.TYPE)
-        @Retention(AnnotationRetention.SOURCE)
-        @IntDef(PARTICIPANT)
-        public annotation class Extensions
-
-        /** Represents the [ParticipantExtension] extension */
-        internal const val PARTICIPANT = 1
-
-        // Represents a null Participant over Binder
-        internal const val NULL_PARTICIPANT_ID = -1
-    }
+    /** Represents the [ParticipantExtension] extension */
+    internal const val PARTICIPANT = 1
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantAction.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantAction.kt
index 24d80cb..b1c0da8 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantAction.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantAction.kt
@@ -16,84 +16,42 @@
 
 package androidx.core.telecom.extensions
 
-import android.os.Build
-import android.util.Log
-import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallControlResult
-import androidx.core.telecom.CallException
-import androidx.core.telecom.internal.ParticipantActionsRemote
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlin.properties.Delegates
-import kotlinx.coroutines.flow.StateFlow
 
 /**
- * Implements the action to kick a Participant that part of the call and is being tracked via
- * [CallExtensionsScope.addParticipantExtension]
- *
- * @param participants A [StateFlow] representing the current Set of Participants that are in the
- *   call.
+ * The action used to determine if the calling application supports kicking participants and request
+ * to kick [Participant]s in the call.
  */
-// TODO: Refactor to Public API
-@RequiresApi(Build.VERSION_CODES.O)
 @ExperimentalAppActions
-internal class KickParticipantAction(
-    private val participants: StateFlow<Set<Participant>>,
-) {
-    companion object {
-        const val TAG = CallExtensionsScope.TAG + "(KPCA)"
-    }
+public interface KickParticipantAction {
 
     /**
-     * Whether or not kicking participants is supported by the remote.
+     * Whether or not kicking participants is supported by the calling application.
      *
-     * if `true`, then requests to kick participants will be sent to the remote application. If
-     * `false`, then the remote doesn't support this action and requests will fail.
+     * if `true`, then requests to kick participants will be sent to the calling application. If
+     * `false`, then the calling application doesn't support this action and requests will fail.
      *
-     * Should not be queried until [CallExtensionsScope.onConnected] is called.
+     * Must not be queried until [CallExtensionScope.onConnected] is called.
      */
-    var isSupported by Delegates.notNull<Boolean>()
-    // The binder interface that allows this action to send events to the remote
-    private var remoteActions: ParticipantActionsRemote? = null
+    public var isSupported: Boolean
 
     /**
      * Request to kick a [participant] in the call.
      *
-     * Note: This operation succeeding does not mean that the participant was kicked, it only means
-     * that the request was received by the remote application. Any state changes that result from
-     * this operation will be represented by the Set of Participants changing to remove the
-     * requested participant.
+     * Whether or not the [Participant] is allowed to be kicked is up to the calling application, so
+     * requesting to kick a [Participant] may result in no action being taken. For example, the
+     * calling application may choose not to complete a request to kick the host of the call or kick
+     * the [Participant] representing this user.
      *
-     * @param participant The participant to kick
+     * Note: This operation succeeding does not mean that the participant was kicked, it only means
+     * that the request was received and processed by the remote application. If the [Participant]
+     * is indeed kicked, the [CallExtensionScope.addParticipantExtension] `onParticipantsUpdated`
+     * callback will be updated to remove the kicked [Participant].
+     *
+     * @param participant The [Participant] to kick from the call.
      * @return The result of whether or not this request was successfully sent to the remote
-     *   application
+     *   application and processed.
      */
-    suspend fun requestKickParticipant(participant: Participant): CallControlResult {
-        Log.d(TAG, "kickParticipant: participant=$participant")
-        if (remoteActions == null) {
-            Log.w(TAG, "kickParticipant: no binder, isSupported=$isSupported")
-            // TODO: This needs to have its own CallException result
-            return CallControlResult.Error(CallException.ERROR_UNKNOWN)
-        }
-        if (!participants.value.contains(participant)) {
-            Log.d(TAG, "kickParticipant: couldn't find participant=$participant")
-            return CallControlResult.Success()
-        }
-        val cb = ActionsResultCallback()
-        remoteActions?.kickParticipant(participant, cb)
-        val result = cb.waitForResponse()
-        Log.d(TAG, "kickParticipant: participant=$participant, result=$result")
-        return result
-    }
-
-    /** Called when capability exchange has completed and we can initialize this action */
-    internal fun initialize(isSupported: Boolean) {
-        Log.d(TAG, "initialize: isSupported=$isSupported")
-        this.isSupported = isSupported
-    }
-
-    /** Called when the remote application has connected and will receive action event requests */
-    internal fun connect(remote: ParticipantActionsRemote?) {
-        Log.d(TAG, "connect: remote is null=${remote == null}")
-        remoteActions = remote
-    }
+    public suspend fun requestKickParticipant(participant: Participant): CallControlResult
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantActionImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantActionImpl.kt
new file mode 100644
index 0000000..a3208e6
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantActionImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
+import androidx.core.telecom.CallException
+import androidx.core.telecom.internal.ParticipantActionsRemote
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlin.properties.Delegates
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Implements the action to kick a Participant that is part of the call and is being tracked via
+ * [CallExtensionScope.addParticipantExtension]
+ *
+ * @param participants A [StateFlow] representing the current Set of Participants that are in the
+ *   call.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalAppActions::class)
+internal class KickParticipantActionImpl(
+    private val participants: StateFlow<Set<Participant>>,
+) : KickParticipantAction {
+    companion object {
+        const val TAG = CallExtensionScopeImpl.TAG + "(KPCA)"
+    }
+
+    override var isSupported by Delegates.notNull<Boolean>()
+    // The binder interface that allows this action to send events to the remote
+    private var remoteActions: ParticipantActionsRemote? = null
+
+    override suspend fun requestKickParticipant(participant: Participant): CallControlResult {
+        Log.d(TAG, "kickParticipant: participant=$participant")
+        if (remoteActions == null) {
+            Log.w(TAG, "kickParticipant: no binder, isSupported=$isSupported")
+            // TODO: This needs to have its own CallException result
+            return CallControlResult.Error(CallException.ERROR_UNKNOWN)
+        }
+        if (!participants.value.contains(participant)) {
+            Log.d(TAG, "kickParticipant: couldn't find participant=$participant")
+            return CallControlResult.Success()
+        }
+        val cb = ActionsResultCallback()
+        remoteActions?.kickParticipant(participant, cb)
+        val result = cb.waitForResponse()
+        Log.d(TAG, "kickParticipant: participant=$participant, result=$result")
+        return result
+    }
+
+    /** Called when capability exchange has completed and we can initialize this action */
+    internal fun initialize(isSupported: Boolean) {
+        Log.d(TAG, "initialize: isSupported=$isSupported")
+        this.isSupported = isSupported
+    }
+
+    /** Called when the remote application has connected and will receive action event requests */
+    internal fun connect(remote: ParticipantActionsRemote?) {
+        Log.d(TAG, "connect: remote is null=${remote == null}")
+        remoteActions = remote
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantState.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantState.kt
index a41b816..ea7758e 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantState.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/KickParticipantState.kt
@@ -30,7 +30,7 @@
  * @param onKickParticipant The action to perform when a request comes in from the remote
  *   InCallService to kick a participant.
  */
-@ExperimentalAppActions
+@OptIn(ExperimentalAppActions::class)
 internal class KickParticipantState(
     val participants: StateFlow<Set<Participant>>,
     private val onKickParticipant: suspend (Participant) -> Unit
@@ -51,13 +51,15 @@
     }
 
     /**
-     * Registered to be called when the remote InCallService has requested to kick a Participant.
+     * Registers to be called when the remote InCallService has requested to kick a Participant.
      *
-     * @param participant The participant to kick
+     * @param participantId The id of the participant to kick
      */
-    private suspend fun kickParticipant(participant: Participant) {
-        if (!participants.value.contains(participant)) {
-            Log.w(LOG_TAG, "kickParticipant: $participant can not be found")
+    private suspend fun kickParticipant(participantId: String) {
+        val participant =
+            participants.value.firstOrNull { participant -> participant.id == participantId }
+        if (participant == null) {
+            Log.w(LOG_TAG, "kickParticipant: $participantId can not be found")
             return
         }
         Log.d(LOG_TAG, "kickParticipant: kicking $participant")
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Participant.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Participant.kt
new file mode 100644
index 0000000..8ec3275
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/Participant.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.core.telecom.extensions
+
+import androidx.core.telecom.util.ExperimentalAppActions
+
+@ExperimentalAppActions
+internal fun ParticipantParcelable.toParticipant(): Participant {
+    return Participant(id, name)
+}
+
+/**
+ * A representation of aspects of a participant in a Call.
+ *
+ * @param id A unique identifier shared with remote surfaces that represents this participant. This
+ *   value MUST be unique and stable for the life of the call, meaning that this ID should not
+ *   change or be reused for the lifetime of the call.
+ * @param name The name of the Participant, which remote surfaces will display to users.
+ */
+@ExperimentalAppActions
+public class Participant(
+    public val id: String,
+    public val name: CharSequence,
+) {
+
+    internal fun toParticipantParcelable(): ParticipantParcelable {
+        return ParticipantParcelable().also { parcelable ->
+            parcelable.id = id
+            parcelable.name = name
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as Participant
+
+        return id == other.id
+    }
+
+    override fun hashCode(): Int {
+        return id.hashCode()
+    }
+
+    override fun toString(): String {
+        return "Participant[$id]: name=$name"
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtension.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtension.kt
index dcc9b1c..04ad0635 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtension.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtension.kt
@@ -16,215 +16,64 @@
 
 package androidx.core.telecom.extensions
 
-import android.os.Build.VERSION_CODES
-import android.util.Log
-import androidx.annotation.IntDef
-import androidx.annotation.RequiresApi
-import androidx.core.telecom.internal.CapabilityExchangeRepository
-import androidx.core.telecom.internal.ParticipantActionCallbackRepository
-import androidx.core.telecom.internal.ParticipantStateListenerRemote
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
 
 /**
- * Called when a new remove connection to an action is being established. The
- * [ParticipantStateListenerRemote] contains the remote interface used to send both the initial and
- * ongoing updates to the state tracked by the action. Any collection of flows related to updating
- * the remote session should use the provided [CoroutineScope]. For event callbacks from the remote,
- * [ParticipantActionCallbackRepository] should be used to register the callbacks that the action
- * should handle.
- */
-@OptIn(ExperimentalAppActions::class)
-internal typealias ActionConnector =
-    (CoroutineScope, ParticipantActionCallbackRepository, ParticipantStateListenerRemote) -> Unit
-
-/**
- * The participant extension that manages the state of Participants associated with this call as
- * well as allowing participant related actions to register themselves with this extension.
+ * The extension interface used to support notifying remote surfaces (automotive, watch, etc...) of
+ * state related to the [Participant]s in the call.
  *
- * Along with updating the participants in a call to remote surfaces, this extension also allows the
- * following optional actions to be supported:
- * - [addRaiseHandSupport] - Support for allowing a remote surface to show which participants have
- *   their hands raised to the user as well as update the raised hand state of the user.
- * - [addKickParticipantSupport] = Support for allowing a user on a remote surface to kick a
- *   participant.
+ * This interface allows an application to notify remote surfaces of changes to [Participant] state.
+ * Additionally, this interface allows the application to support optional actions that use the
+ * participant state. These actions provide remote surfaces with the ability to request participant
+ * state updates based on user input and provide additional information about the state of specific
+ * participants.
  *
- * @param initialParticipants The initial set of Participants that are associated with this call.
- * @param initialActiveParticipant The initial active Participant that is associated with this call.
+ * @see ExtensionInitializationScope.addParticipantExtension
  */
-// TODO: Refactor to Public API
 @ExperimentalAppActions
-@RequiresApi(VERSION_CODES.O)
-internal class ParticipantExtension(
-    initialParticipants: Set<Participant>,
-    initialActiveParticipant: Participant?
-) {
-    public companion object {
-        /**
-         * The version of this ParticipantExtension used for capability exchange. Should be updated
-         * whenever there is an API change to this extension or an existing action.
-         */
-        internal const val VERSION = 1
-
-        /**
-         * Constants used to denote the type of action supported by the [Capability] being
-         * registered.
-         */
-        @Target(AnnotationTarget.TYPE)
-        @Retention(AnnotationRetention.SOURCE)
-        @IntDef(RAISE_HAND_ACTION, KICK_PARTICIPANT_ACTION)
-        annotation class ExtensionActions
-
-        /** Identifier for the raise hand action */
-        internal const val RAISE_HAND_ACTION = 1
-        /** Identifier for the kick participant action */
-        internal const val KICK_PARTICIPANT_ACTION = 2
-
-        private const val LOG_TAG = Extensions.LOG_TAG + "(PE)"
-    }
-
-    /** StateFlow of the current set of Participants associated with the call */
-    internal val participants: MutableStateFlow<Set<Participant>> =
-        MutableStateFlow(initialParticipants)
-
-    /** StateFlow containing the active participant of the call if it exists */
-    private val activeParticipant: MutableStateFlow<Participant?> =
-        MutableStateFlow(initialActiveParticipant)
-
-    /** Maps an action to its [ActionConnector], which will be called during capability exchange */
-    private val actionRemoteConnector: HashMap<Int, ActionConnector> = HashMap()
-
+public interface ParticipantExtension {
     /**
-     * Update all remote listeners that the Participants of this call have changed
+     * Update all of the remote surfaces that the [Participant]s of this call have changed.
      *
-     * @param newParticipants The new set of [Participant]s associated with this call
+     * @param newParticipants The new set of [Participant]s associated with this call.
      */
-    public suspend fun updateParticipants(newParticipants: Set<Participant>) {
-        participants.emit(newParticipants)
-    }
+    public suspend fun updateParticipants(newParticipants: Set<Participant>)
 
     /**
-     * The active participant associated with this call, if it exists
+     * Update all of the remote surfaces that the active participant associated with this call has
+     * changed, if it exists.
      *
-     * @param participant the participant that is marked as active or `null` if there is no active
-     *   participant
-     */
-    public suspend fun updateActiveParticipant(participant: Participant?) {
-        activeParticipant.emit(participant)
-    }
-
-    /**
-     * Adds support for notifying remote InCallServices of the raised hand state of all Participants
-     * in the call and listening for changes to this user's hand raised state.
+     * The "active" participant is the participant that is currently taking focus and should be
+     * marked in UX as active or take a more prominent view to the user.
      *
-     * @param onHandRaisedChanged Called when the raised hand state of this user has changed. If
-     *   `true`, the user has raised their hand. If `false`, the user has lowered their hand.
-     * @return The interface used to update the current raised hand state of all participants in the
-     *   call.
+     * @param participant the [Participant] that is marked as the active participant or `null` if
+     *   there is no active participant
      */
-    fun addRaiseHandSupport(onHandRaisedChanged: suspend (Boolean) -> Unit): RaiseHandState {
-        val state = RaiseHandState(participants, onHandRaisedChanged)
-        registerAction(RAISE_HAND_ACTION, connector = state::connect)
-        return state
-    }
+    public suspend fun updateActiveParticipant(participant: Participant?)
 
     /**
-     * Adds support for allowing the user to kick participants in the call.
+     * Adds support for notifying remote surfaces of the "raised hand" state of all [Participant]s
+     * in the call.
      *
-     * @param onKickParticipant The action to perform when the user requests to kick a participant
-     * @return The interface used to update the state related to this action. This action contains
-     *   no state today, but is included for forward compatibility
+     * @param initialRaisedHands The initial List of [Participant]s whose hands are raised, ordered
+     *   from earliest raised hand to newest raised hand.
+     * @param onHandRaisedChanged This is called when the user has requested to change their "raised
+     *   hand" state on a remote surface. If `true`, this user has raised their hand. If `false`,
+     *   this user has lowered their hand. This operation should not return until the request has
+     *   been processed.
+     * @return The interface used to update the current raised hand state of all [Participant]s in
+     *   the call.
      */
-    fun addKickParticipantSupport(onKickParticipant: suspend (Participant) -> Unit) {
-        val state = KickParticipantState(participants, onKickParticipant)
-        registerAction(KICK_PARTICIPANT_ACTION) { _, repo, _ -> state.connect(repo) }
-    }
+    public fun addRaiseHandSupport(
+        initialRaisedHands: List<Participant> = emptyList(),
+        onHandRaisedChanged: suspend (Boolean) -> Unit
+    ): RaiseHandState
 
     /**
-     * Setup the participant extension creation callback receiver and return the Capability of this
-     * extension to be shared with the remote.
-     */
-    internal fun onExchangeStarted(callbacks: CapabilityExchangeRepository): Capability {
-        callbacks.onCreateParticipantExtension = ::onCreateParticipantExtension
-        return Capability().apply {
-            featureId = Extensions.PARTICIPANT
-            featureVersion = VERSION
-            supportedActions = actionRemoteConnector.keys.toIntArray()
-        }
-    }
-
-    /**
-     * Register an action to this extension
+     * Adds support for allowing the user to kick participants in the call using the remote surface.
      *
-     * @param action The identifier of the action, which will be shared with the remote
-     * @param connector The method that is called every time a new remote connects to the action in
-     *   order to facilitate connecting this action to the remote.
+     * @param onKickParticipant The action to perform when the user requests to kick a participant.
+     *   This operation should not return until the request has been processed.
      */
-    private fun registerAction(action: Int, connector: ActionConnector) {
-        actionRemoteConnector[action] = connector
-    }
-
-    /**
-     * Function registered to [ExtensionInitializationScope] in order to handle the creation of the
-     * participant extension.
-     *
-     * @param coroutineScope the CoroutineScope used to launch tasks associated with participants
-     * @param remoteActions the actions reported as supported from the remote InCallService side
-     * @param binder the interface used to communicate with the remote InCallService.
-     */
-    private fun onCreateParticipantExtension(
-        coroutineScope: CoroutineScope,
-        remoteActions: Set<Int>,
-        binder: ParticipantStateListenerRemote
-    ) {
-        Log.i(LOG_TAG, "onCreatePE: actions=$remoteActions")
-
-        // Synchronize initial state with remote
-        val initParticipants = participants.value.toTypedArray()
-        val initActiveParticipant = activeParticipant.value
-        binder.updateParticipants(initParticipants)
-        if (initActiveParticipant != null && initParticipants.contains(initActiveParticipant)) {
-            binder.updateActiveParticipant(initActiveParticipant.id)
-        } else {
-            binder.updateActiveParticipant(Extensions.NULL_PARTICIPANT_ID)
-        }
-
-        // Setup listeners for changes to state
-        participants
-            .onEach { updatedParticipants ->
-                Log.i(LOG_TAG, "to remote: updateParticipants: $updatedParticipants")
-                binder.updateParticipants(updatedParticipants.toTypedArray())
-            }
-            .combine(activeParticipant) { p, a ->
-                val result = if (a != null && p.contains(a)) a else null
-                Log.d(LOG_TAG, "combine: $p + $a = $result")
-                result
-            }
-            .distinctUntilChanged()
-            .onEach {
-                Log.d(LOG_TAG, "to remote: updateActiveParticipant=$it")
-                binder.updateActiveParticipant(it?.id ?: Extensions.NULL_PARTICIPANT_ID)
-            }
-            .launchIn(coroutineScope)
-        Log.d(LOG_TAG, "onCreatePE: finished state update")
-
-        // Setup actions
-        coroutineScope.launch {
-            // Setup one callback repository per connection to remote
-            val callbackRepository = ParticipantActionCallbackRepository(this)
-            // Set up actions (only where the remote side supports it)
-            actionRemoteConnector
-                .filter { entry -> remoteActions.contains(entry.key) }
-                .map { entry -> entry.value }
-                .forEach { initializer -> initializer(this, callbackRepository, binder) }
-            Log.d(LOG_TAG, "onCreatePE: calling finishSync")
-            binder.finishSync(callbackRepository.eventListener)
-        }
-    }
+    public fun addKickParticipantSupport(onKickParticipant: suspend (Participant) -> Unit)
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionImpl.kt
new file mode 100644
index 0000000..e3d1caa
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionImpl.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build.VERSION_CODES
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.internal.CapabilityExchangeRepository
+import androidx.core.telecom.internal.ParticipantActionCallbackRepository
+import androidx.core.telecom.internal.ParticipantStateListenerRemote
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Called when a new remove connection to an action is being established. The
+ * [ParticipantStateListenerRemote] contains the remote interface used to send both the initial and
+ * ongoing updates to the state tracked by the action. Any collection of flows related to updating
+ * the remote session should use the provided [CoroutineScope]. For event callbacks from the remote,
+ * [ParticipantActionCallbackRepository] should be used to register the callbacks that the action
+ * should handle.
+ */
+@OptIn(ExperimentalAppActions::class)
+internal typealias ActionConnector =
+    (CoroutineScope, ParticipantActionCallbackRepository, ParticipantStateListenerRemote) -> Unit
+
+/**
+ * The participant extension that manages the state of Participants associated with this call as
+ * well as allowing participant related actions to register themselves with this extension.
+ *
+ * Along with updating the participants in a call to remote surfaces, this extension also allows the
+ * following optional actions to be supported:
+ * - [addRaiseHandSupport] - Support for allowing a remote surface to show which participants have
+ *   their hands raised to the user as well as update the raised hand state of the user.
+ * - [addKickParticipantSupport] = Support for allowing a user on a remote surface to kick a
+ *   participant.
+ *
+ * @param initialParticipants The initial set of Participants that are associated with this call.
+ * @param initialActiveParticipant The initial active Participant that is associated with this call.
+ */
+@OptIn(ExperimentalAppActions::class)
+@RequiresApi(VERSION_CODES.O)
+internal class ParticipantExtensionImpl(
+    initialParticipants: Set<Participant>,
+    initialActiveParticipant: Participant?
+) : ParticipantExtension {
+    companion object {
+        /**
+         * The version of this ParticipantExtension used for capability exchange. Should be updated
+         * whenever there is an API change to this extension or an existing action.
+         */
+        internal const val VERSION = 1
+
+        /**
+         * Constants used to denote the type of action supported by the [Capability] being
+         * registered.
+         */
+        @Target(AnnotationTarget.TYPE)
+        @Retention(AnnotationRetention.SOURCE)
+        @IntDef(RAISE_HAND_ACTION, KICK_PARTICIPANT_ACTION)
+        annotation class ExtensionActions
+
+        /** Identifier for the raise hand action */
+        internal const val RAISE_HAND_ACTION = 1
+        /** Identifier for the kick participant action */
+        internal const val KICK_PARTICIPANT_ACTION = 2
+
+        private const val LOG_TAG = Extensions.LOG_TAG + "(PE)"
+    }
+
+    /** StateFlow of the current set of Participants associated with the call */
+    internal val participants: MutableStateFlow<Set<Participant>> =
+        MutableStateFlow(initialParticipants)
+
+    /** StateFlow containing the active participant of the call if it exists */
+    private val activeParticipant: MutableStateFlow<Participant?> =
+        MutableStateFlow(initialActiveParticipant)
+
+    /** Maps an action to its [ActionConnector], which will be called during capability exchange */
+    private val actionRemoteConnector: HashMap<Int, ActionConnector> = HashMap()
+
+    override suspend fun updateParticipants(newParticipants: Set<Participant>) {
+        participants.emit(newParticipants)
+    }
+
+    override suspend fun updateActiveParticipant(participant: Participant?) {
+        activeParticipant.emit(participant)
+    }
+
+    override fun addRaiseHandSupport(
+        initialRaisedHands: List<Participant>,
+        onHandRaisedChanged: suspend (Boolean) -> Unit
+    ): RaiseHandState {
+        val state = RaiseHandStateImpl(participants, initialRaisedHands, onHandRaisedChanged)
+        registerAction(RAISE_HAND_ACTION, connector = state::connect)
+        return state
+    }
+
+    override fun addKickParticipantSupport(onKickParticipant: suspend (Participant) -> Unit) {
+        val state = KickParticipantState(participants, onKickParticipant)
+        registerAction(KICK_PARTICIPANT_ACTION) { _, repo, _ -> state.connect(repo) }
+    }
+
+    /**
+     * Setup the participant extension creation callback receiver and return the Capability of this
+     * extension to be shared with the remote.
+     */
+    internal fun onExchangeStarted(callbacks: CapabilityExchangeRepository): Capability {
+        callbacks.onCreateParticipantExtension = ::onCreateParticipantExtension
+        return Capability().apply {
+            featureId = Extensions.PARTICIPANT
+            featureVersion = VERSION
+            supportedActions = actionRemoteConnector.keys.toIntArray()
+        }
+    }
+
+    /**
+     * Register an action to this extension
+     *
+     * @param action The identifier of the action, which will be shared with the remote
+     * @param connector The method that is called every time a new remote connects to the action in
+     *   order to facilitate connecting this action to the remote.
+     */
+    private fun registerAction(action: Int, connector: ActionConnector) {
+        actionRemoteConnector[action] = connector
+    }
+
+    /**
+     * Function registered to [ExtensionInitializationScope] in order to handle the creation of the
+     * participant extension.
+     *
+     * @param coroutineScope the CoroutineScope used to launch tasks associated with participants
+     * @param remoteActions the actions reported as supported from the remote InCallService side
+     * @param binder the interface used to communicate with the remote InCallService.
+     */
+    private fun onCreateParticipantExtension(
+        coroutineScope: CoroutineScope,
+        remoteActions: Set<Int>,
+        binder: ParticipantStateListenerRemote
+    ) {
+        Log.i(LOG_TAG, "onCreatePE: actions=$remoteActions")
+
+        // Synchronize initial state with remote
+        val initParticipants = participants.value
+        val initActiveParticipant = activeParticipant.value
+        binder.updateParticipants(initParticipants)
+        if (initActiveParticipant != null && initParticipants.contains(initActiveParticipant)) {
+            binder.updateActiveParticipant(initActiveParticipant)
+        } else {
+            binder.updateActiveParticipant(null)
+        }
+
+        // Setup listeners for changes to state
+        participants
+            .onEach { updatedParticipants ->
+                Log.i(LOG_TAG, "to remote: updateParticipants: $updatedParticipants")
+                binder.updateParticipants(updatedParticipants)
+            }
+            .combine(activeParticipant) { p, a ->
+                val result = if (a != null && p.contains(a)) a else null
+                Log.d(LOG_TAG, "combine: $p + $a = $result")
+                result
+            }
+            .distinctUntilChanged()
+            .onEach {
+                Log.d(LOG_TAG, "to remote: updateActiveParticipant=$it")
+                binder.updateActiveParticipant(it)
+            }
+            .launchIn(coroutineScope)
+        Log.d(LOG_TAG, "onCreatePE: finished state update")
+
+        // Setup one callback repository per connection to remote
+        val callbackRepository = ParticipantActionCallbackRepository(coroutineScope)
+        // Set up actions (only where the remote side supports it)
+        actionRemoteConnector
+            .filter { entry -> remoteActions.contains(entry.key) }
+            .map { entry -> entry.value }
+            .forEach { initializer -> initializer(coroutineScope, callbackRepository, binder) }
+        Log.d(LOG_TAG, "onCreatePE: calling finishSync")
+        binder.finishSync(callbackRepository.eventListener)
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemote.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemote.kt
index f0b2482..f7346c7 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemote.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemote.kt
@@ -16,99 +16,42 @@
 
 package androidx.core.telecom.extensions
 
-import android.os.Build
-import android.util.Log
-import androidx.annotation.RequiresApi
-import androidx.core.telecom.internal.CapabilityExchangeListenerRemote
-import androidx.core.telecom.internal.ParticipantActionsRemote
-import androidx.core.telecom.internal.ParticipantStateListener
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlin.properties.Delegates
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-
-/** Repository containing the callbacks associated with the Participant extension state changes */
-@ExperimentalAppActions
-internal class ParticipantStateCallbackRepository {
-    var raisedHandsStateCallback: (suspend (Set<Int>) -> Unit)? = null
-}
 
 /**
- * Contains the callbacks used by Actions during creation. [onInitialization] is called when
- * capability exchange has completed and Actions should be initialized and [onRemoteConnected] is
- * called when the remote has connected, finished sending initial state, and is ready to handle
- * Participant action updates.
- */
-@ExperimentalAppActions
-private data class ActionExchangeResult(
-    val onInitialization: (Boolean) -> Unit,
-    val onRemoteConnected: (ParticipantActionsRemote?) -> Unit
-)
-
-/**
- * Implements the Participant extension and provides a method for actions to use to register
- * themselves.
+ * Interface used to allow the remote surface (automotive, watch, etc...) to know if the connected
+ * calling application supports the participant extension and optionally set up additional actions
+ * for the [Participant]s in the call.
  *
- * @param callScope The CoroutineScope of the underlying call
- * @param onActiveParticipantChanged The update callback used whenever the active participants
- *   change
- * @param onParticipantsUpdated The update callback used whenever the participants in the call
- *   change
+ * Actions allow the remote surface to display additional optional state regarding the
+ * [Participant]s in the call and send action requests to the calling application to modify the
+ * state of supported actions.
  */
-// TODO: Refactor to Public API
-// TODO: Remove old version of ParticipantClientExtension in a follow up CL with this impl.
-@RequiresApi(Build.VERSION_CODES.O)
 @ExperimentalAppActions
-internal class ParticipantExtensionRemote(
-    private val callScope: CoroutineScope,
-    private val onActiveParticipantChanged: suspend (Participant?) -> Unit,
-    private val onParticipantsUpdated: suspend (Set<Participant>) -> Unit
-) {
-    companion object {
-        internal const val TAG = CallExtensionsScope.TAG + "(PCE)"
-    }
+public interface ParticipantExtensionRemote {
 
     /**
-     * Whether or not the participants extension is supported by the remote.
+     * Whether or not the participants extension is supported by the calling application.
      *
-     * if `true`, then updates about call participants will be notified. If `false`, then the remote
-     * doesn't support this extension and participants will not be notified to the caller nor will
-     * associated actions receive state updates.
+     * If `true`, then updates about [Participant]s in the call will be notified. If `false`, then
+     * the remote doesn't support this extension and participants will not be notified to the caller
+     * nor will associated actions receive state updates.
      *
-     * Should not be queried until [CallExtensionsScope.onConnected] is called.
+     * Note: Must not be queried until after [CallExtensionScope.onConnected] is called.
      */
-    var isSupported by Delegates.notNull<Boolean>()
-
-    /** The actions that are registered with the Participant extension */
-    internal val actions
-        get() = actionInitializers.keys.toIntArray()
-
-    // Maps a Capability to a receiver that allows the action to register itself with a listener
-    // and then return a Receiver that gets called when Cap exchange completes.
-    private val actionInitializers = HashMap<Int, ActionExchangeResult>()
-    // Manages callbacks that are applicable to sub-actions of the Participants
-    private val callbacks = ParticipantStateCallbackRepository()
-
-    // Participant specific state
-    private val participants = MutableStateFlow<Set<Participant>>(emptySet())
-    private val activeParticipant = MutableStateFlow<Int?>(null)
+    public val isSupported: Boolean
 
     /**
-     * Adds the ability for participants to raise their hands.
+     * Adds the "raise hand" action and provides the remote surface with the ability to display
+     * which [Participant]s have their hands raised and an action to request to raise and lower
+     * their own hand.
      *
      * ```
      * connectExtensions(call) {
      *     val participantExtension = addParticipantExtension(
      *         // consume participant changed events
      *     )
+     *     // Initialize the raise hand action
      *     val raiseHandAction = participantExtension.addRaiseHandAction { raisedHands ->
      *         // consume changes of participants with their hands raised
      *     }
@@ -121,25 +64,19 @@
      * }
      * ```
      *
-     * @param onRaisedHandsChanged Called when the Set of Participants with their hands raised has
-     *   changed.
-     * @return The action that is used to send raise hand event requests to the remote Call.
+     * Note: Must be called during initialization before [CallExtensionScope.onConnected] is called.
+     *
+     * @param onRaisedHandsChanged Called when the List of [Participant]s with their hands raised
+     *   has changed, ordered from oldest raised hand to newest raised hand.
+     * @return The action that is used to determine support of this action and send raise hand event
+     *   requests to the calling application.
      */
-    fun addRaiseHandAction(
-        onRaisedHandsChanged: suspend (Set<Participant>) -> Unit
-    ): RaiseHandAction {
-        val action = RaiseHandAction(participants, onRaisedHandsChanged)
-        registerAction(
-            ParticipantExtension.RAISE_HAND_ACTION,
-            onRemoteConnected = action::connect
-        ) { isSupported ->
-            action.initialize(callScope, isSupported, callbacks)
-        }
-        return action
-    }
+    public fun addRaiseHandAction(
+        onRaisedHandsChanged: suspend (List<Participant>) -> Unit
+    ): RaiseHandAction
 
     /**
-     * Adds the ability for the user to kick participants.
+     * Adds the ability for the user to request to kick [Participant]s in the call.
      *
      * ```
      * connectExtensions(call) {
@@ -159,158 +96,5 @@
      *
      * @return The action that is used to send kick Participant event requests to the remote Call.
      */
-    fun addKickParticipantAction(): KickParticipantAction {
-        val action = KickParticipantAction(participants)
-        registerAction(
-            ParticipantExtension.KICK_PARTICIPANT_ACTION,
-            onRemoteConnected = action::connect,
-            onInitialization = action::initialize
-        )
-        return action
-    }
-
-    /**
-     * Register an Action on the Participant extension that will be initialized and connected if the
-     * action is supported by the remote Call before [CallExtensionsScope.onConnected] is called.
-     *
-     * @param action A unique identifier for the action that will be used by the remote side to
-     *   identify this action.
-     * @param onRemoteConnected The callback called when the remote has connected and action events
-     *   can be sent to the remote via [ParticipantActionsRemote].
-     * @param onInitialization The Action initializer, which allows the action to setup callbacks.
-     *   via [ParticipantStateCallbackRepository] and determine if the action is supported by the
-     *   remote Call.
-     */
-    private fun registerAction(
-        action: Int,
-        onRemoteConnected: (ParticipantActionsRemote?) -> Unit,
-        onInitialization: (Boolean) -> Unit
-    ) {
-        actionInitializers[action] = ActionExchangeResult(onInitialization, onRemoteConnected)
-    }
-
-    /**
-     * Capability exchange has completed and the [Capability] of the Participant extension has been
-     * negotiated with the remote call.
-     *
-     * @param negotiatedCapability The negotiated Participant capability or null if the remote
-     *   doesn't support this capability.
-     * @param remote The remote interface which must be used by this extension to create the
-     *   Participant extension on the remote side using the negotiated capability.
-     */
-    internal suspend fun onExchangeComplete(
-        negotiatedCapability: Capability?,
-        remote: CapabilityExchangeListenerRemote?
-    ) {
-        if (negotiatedCapability == null || remote == null) {
-            Log.i(TAG, "onNegotiated: remote is not capable")
-            isSupported = false
-            initializeNotSupportedActions()
-            return
-        }
-        Log.d(TAG, "onNegotiated: setup updates")
-        initializeParticipantUpdates()
-        initializeActionsLocally(negotiatedCapability)
-        val remoteBinder = connectActionsToRemote(negotiatedCapability, remote)
-        actionInitializers.forEach { connector -> connector.value.onRemoteConnected(remoteBinder) }
-    }
-
-    /**
-     * Connect Participant action Flows to the remote interface so we can start receiving changes to
-     * the Participant and associated action state.
-     *
-     * When [CapabilityExchangeListenerRemote.onCreateParticipantExtension] is called, the remote
-     * will send the initial state of each of the supported actions and then call
-     * [ParticipantStateListener.finishSync], which will provide us an interface to allow us to send
-     * participant action event requests.
-     *
-     * @param negotiatedCapability The negotiated Participant capability that contains a negotiated
-     *   version and actions supported by both the local and remote Call.
-     * @param remote The interface used by the local call to create the Participant extension with
-     *   the remote party if supported and allow for Participant state updates.
-     * @return The interface used by the local Call to send Participant action event requests.
-     */
-    private suspend fun connectActionsToRemote(
-        negotiatedCapability: Capability,
-        remote: CapabilityExchangeListenerRemote
-    ): ParticipantActionsRemote? = suspendCoroutine { continuation ->
-        val participantStateListener =
-            ParticipantStateListener(
-                updateParticipants = { newParticipants ->
-                    callScope.launch {
-                        Log.v(TAG, "updateParticipants: $newParticipants")
-                        participants.emit(newParticipants)
-                    }
-                },
-                updateActiveParticipant = { newActiveParticipant ->
-                    callScope.launch {
-                        Log.v(TAG, "activeParticipant=$newActiveParticipant")
-                        activeParticipant.emit(newActiveParticipant)
-                    }
-                },
-                updateRaisedHands = { newRaisedHands ->
-                    callScope.launch {
-                        Log.v(TAG, "raisedHands=$newRaisedHands")
-                        callbacks.raisedHandsStateCallback?.invoke(newRaisedHands)
-                    }
-                },
-                finishSync = { remoteBinder ->
-                    callScope.launch {
-                        Log.v(TAG, "finishSync complete, isNull=${remoteBinder == null}")
-                        continuation.resume(remoteBinder)
-                    }
-                }
-            )
-        remote.onCreateParticipantExtension(
-            negotiatedCapability.featureVersion,
-            negotiatedCapability.supportedActions,
-            participantStateListener
-        )
-    }
-
-    /** Setup callback updates when [participants] or [activeParticipant] changes */
-    private fun initializeParticipantUpdates() {
-        participants
-            .onEach { participantsState -> onParticipantsUpdated(participantsState) }
-            .combine(activeParticipant) { p, ap ->
-                ap?.let { p.firstOrNull { participant -> participant.id == ap } }
-            }
-            .distinctUntilChanged()
-            .onEach { activeParticipant -> onActiveParticipantChanged(activeParticipant) }
-            .onCompletion { Log.d(TAG, "participant flow complete") }
-            .launchIn(callScope)
-    }
-
-    /**
-     * Calls the [ActionExchangeResult.onInitialization] callback on each registered action
-     * (registered via [registerAction]) to initialize. Initialization uses the negotiated
-     * [Capability] to determine whether or not the registered action is supported by the remote and
-     * provides the ability for the action to register for remote state callbacks.
-     *
-     * @param negotiatedCapability The negotiated Participant [Capability] containing the
-     *   Participant extension version and actions supported by both the local and remote Call.
-     */
-    private fun initializeActionsLocally(negotiatedCapability: Capability) {
-        for (action in actionInitializers) {
-            Log.d(TAG, "initializeActions: setup action=${action.key}")
-            if (negotiatedCapability.supportedActions.contains(action.key)) {
-                Log.d(TAG, "initializeActions: action=${action.key} supported")
-                action.value.onInitialization(true)
-            } else {
-                Log.d(TAG, "initializeActions: action=${action.key} not supported")
-                action.value.onInitialization(false)
-            }
-        }
-    }
-
-    /**
-     * In the case that participants are not supported, notify all actions that they are also not
-     * supported.
-     */
-    private fun initializeNotSupportedActions() {
-        Log.d(TAG, "initializeActions: no actions supported")
-        for (action in actionInitializers) {
-            action.value.onInitialization(false)
-        }
-    }
+    public fun addKickParticipantAction(): KickParticipantAction
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemoteImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemoteImpl.kt
new file mode 100644
index 0000000..012e7bc
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ParticipantExtensionRemoteImpl.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.internal.CapabilityExchangeListenerRemote
+import androidx.core.telecom.internal.ParticipantActionsRemote
+import androidx.core.telecom.internal.ParticipantStateListener
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlin.properties.Delegates
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/** Repository containing the callbacks associated with the Participant extension state changes */
+@ExperimentalAppActions
+internal class ParticipantStateCallbackRepository {
+    var raisedHandIdsStateCallback: (suspend (List<String>) -> Unit)? = null
+}
+
+/**
+ * Contains the callbacks used by Actions during creation. [onInitialization] is called when
+ * capability exchange has completed and Actions should be initialized and [onRemoteConnected] is
+ * called when the remote has connected, finished sending initial state, and is ready to handle
+ * Participant action updates.
+ */
+@ExperimentalAppActions
+private data class ActionExchangeResult(
+    val onInitialization: (Boolean) -> Unit,
+    val onRemoteConnected: (ParticipantActionsRemote?) -> Unit
+)
+
+/**
+ * Implements the Participant extension and provides a method for actions to use to register
+ * themselves.
+ *
+ * @param callScope The CoroutineScope of the underlying call
+ * @param onActiveParticipantChanged The update callback used whenever the active participants
+ *   change
+ * @param onParticipantsUpdated The update callback used whenever the participants in the call
+ *   change
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalAppActions::class)
+internal class ParticipantExtensionRemoteImpl(
+    private val callScope: CoroutineScope,
+    private val onActiveParticipantChanged: suspend (Participant?) -> Unit,
+    private val onParticipantsUpdated: suspend (Set<Participant>) -> Unit
+) : ParticipantExtensionRemote {
+    companion object {
+        internal const val TAG = CallExtensionScopeImpl.TAG + "(PCE)"
+    }
+
+    override var isSupported by Delegates.notNull<Boolean>()
+
+    /** The actions that are registered with the Participant extension */
+    internal val actions
+        get() = actionInitializers.keys.toIntArray()
+
+    // Maps a Capability to a receiver that allows the action to register itself with a listener
+    // and then return a Receiver that gets called when Cap exchange completes.
+    private val actionInitializers = HashMap<Int, ActionExchangeResult>()
+    // Manages callbacks that are applicable to sub-actions of the Participants
+    private val callbacks = ParticipantStateCallbackRepository()
+
+    // Participant specific state
+    private val participants = MutableStateFlow<Set<Participant>>(emptySet())
+    private val activeParticipantId = MutableStateFlow<String?>(null)
+
+    override fun addRaiseHandAction(
+        onRaisedHandsChanged: suspend (List<Participant>) -> Unit
+    ): RaiseHandAction {
+        val action = RaiseHandActionImpl(participants, onRaisedHandsChanged)
+        registerAction(
+            ParticipantExtensionImpl.RAISE_HAND_ACTION,
+            onRemoteConnected = action::connect
+        ) { isSupported ->
+            action.initialize(callScope, isSupported, callbacks)
+        }
+        return action
+    }
+
+    override fun addKickParticipantAction(): KickParticipantAction {
+        val action = KickParticipantActionImpl(participants)
+        registerAction(
+            ParticipantExtensionImpl.KICK_PARTICIPANT_ACTION,
+            onRemoteConnected = action::connect,
+            onInitialization = action::initialize
+        )
+        return action
+    }
+
+    /**
+     * Register an Action on the Participant extension that will be initialized and connected if the
+     * action is supported by the remote Call before [CallExtensionScope.onConnected] is called.
+     *
+     * @param action A unique identifier for the action that will be used by the remote side to
+     *   identify this action.
+     * @param onRemoteConnected The callback called when the remote has connected and action events
+     *   can be sent to the remote via [ParticipantActionsRemote].
+     * @param onInitialization The Action initializer, which allows the action to setup callbacks.
+     *   via [ParticipantStateCallbackRepository] and determine if the action is supported by the
+     *   remote Call.
+     */
+    private fun registerAction(
+        action: Int,
+        onRemoteConnected: (ParticipantActionsRemote?) -> Unit,
+        onInitialization: (Boolean) -> Unit
+    ) {
+        actionInitializers[action] = ActionExchangeResult(onInitialization, onRemoteConnected)
+    }
+
+    /**
+     * Capability exchange has completed and the [Capability] of the Participant extension has been
+     * negotiated with the remote call.
+     *
+     * @param negotiatedCapability The negotiated Participant capability or null if the remote
+     *   doesn't support this capability.
+     * @param remote The remote interface which must be used by this extension to create the
+     *   Participant extension on the remote side using the negotiated capability.
+     */
+    internal suspend fun onExchangeComplete(
+        negotiatedCapability: Capability?,
+        remote: CapabilityExchangeListenerRemote?
+    ) {
+        if (negotiatedCapability == null || remote == null) {
+            Log.i(TAG, "onNegotiated: remote is not capable")
+            isSupported = false
+            initializeNotSupportedActions()
+            return
+        }
+        Log.d(TAG, "onNegotiated: setup updates")
+        initializeParticipantUpdates()
+        initializeActionsLocally(negotiatedCapability)
+        val remoteBinder = connectActionsToRemote(negotiatedCapability, remote)
+        actionInitializers.forEach { connector -> connector.value.onRemoteConnected(remoteBinder) }
+    }
+
+    /**
+     * Connect Participant action Flows to the remote interface so we can start receiving changes to
+     * the Participant and associated action state.
+     *
+     * When [CapabilityExchangeListenerRemote.onCreateParticipantExtension] is called, the remote
+     * will send the initial state of each of the supported actions and then call
+     * [ParticipantStateListener.finishSync], which will provide us an interface to allow us to send
+     * participant action event requests.
+     *
+     * @param negotiatedCapability The negotiated Participant capability that contains a negotiated
+     *   version and actions supported by both the local and remote Call.
+     * @param remote The interface used by the local call to create the Participant extension with
+     *   the remote party if supported and allow for Participant state updates.
+     * @return The interface used by the local Call to send Participant action event requests.
+     */
+    private suspend fun connectActionsToRemote(
+        negotiatedCapability: Capability,
+        remote: CapabilityExchangeListenerRemote
+    ): ParticipantActionsRemote? = suspendCoroutine { continuation ->
+        val participantStateListener =
+            ParticipantStateListener(
+                updateParticipants = { newParticipants ->
+                    callScope.launch {
+                        Log.v(TAG, "updateParticipants: $newParticipants")
+                        participants.emit(newParticipants)
+                    }
+                },
+                updateActiveParticipantId = { newActiveParticipant ->
+                    callScope.launch {
+                        Log.v(TAG, "activeParticipant=$newActiveParticipant")
+                        activeParticipantId.emit(newActiveParticipant)
+                    }
+                },
+                updateRaisedHandIds = { newRaisedHands ->
+                    callScope.launch {
+                        Log.v(TAG, "raisedHands=$newRaisedHands")
+                        callbacks.raisedHandIdsStateCallback?.invoke(newRaisedHands)
+                    }
+                },
+                finishSync = { remoteBinder ->
+                    callScope.launch {
+                        Log.v(TAG, "finishSync complete, isNull=${remoteBinder == null}")
+                        continuation.resume(remoteBinder)
+                    }
+                }
+            )
+        remote.onCreateParticipantExtension(
+            negotiatedCapability.featureVersion,
+            negotiatedCapability.supportedActions,
+            participantStateListener
+        )
+    }
+
+    /** Setup callback updates when [participants] or [activeParticipantId] changes */
+    private fun initializeParticipantUpdates() {
+        participants
+            .onEach { participantsState -> onParticipantsUpdated(participantsState) }
+            .combine(activeParticipantId) { p, ap ->
+                ap?.let { p.firstOrNull { participant -> participant.id == ap } }
+            }
+            .distinctUntilChanged()
+            .onEach { activeParticipant -> onActiveParticipantChanged(activeParticipant) }
+            .onCompletion { Log.d(TAG, "participant flow complete") }
+            .launchIn(callScope)
+    }
+
+    /**
+     * Calls the [ActionExchangeResult.onInitialization] callback on each registered action
+     * (registered via [registerAction]) to initialize. Initialization uses the negotiated
+     * [Capability] to determine whether or not the registered action is supported by the remote and
+     * provides the ability for the action to register for remote state callbacks.
+     *
+     * @param negotiatedCapability The negotiated Participant [Capability] containing the
+     *   Participant extension version and actions supported by both the local and remote Call.
+     */
+    private fun initializeActionsLocally(negotiatedCapability: Capability) {
+        for (action in actionInitializers) {
+            Log.d(TAG, "initializeActions: setup action=${action.key}")
+            if (negotiatedCapability.supportedActions.contains(action.key)) {
+                Log.d(TAG, "initializeActions: action=${action.key} supported")
+                action.value.onInitialization(true)
+            } else {
+                Log.d(TAG, "initializeActions: action=${action.key} not supported")
+                action.value.onInitialization(false)
+            }
+        }
+    }
+
+    /**
+     * In the case that participants are not supported, notify all actions that they are also not
+     * supported.
+     */
+    private fun initializeNotSupportedActions() {
+        Log.d(TAG, "initializeActions: no actions supported")
+        for (action in actionInitializers) {
+            action.value.onInitialization(false)
+        }
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandAction.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandAction.kt
index 4328d83..5082d4e 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandAction.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandAction.kt
@@ -16,110 +16,43 @@
 
 package androidx.core.telecom.extensions
 
-import android.os.Build
-import android.util.Log
-import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallControlResult
-import androidx.core.telecom.CallException
-import androidx.core.telecom.internal.ParticipantActionsRemote
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlin.properties.Delegates
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
 
 /**
- * Implements the ability for the user to raise/lower their hand as well as allow the user to listen
- * to the hand raised states of all other participants
- *
- * @param participants The StateFlow containing the current set of participants in the call at any
- *   given time.
- * @param onRaisedHandsChanged The callback that allows the user to listen to the state of
- *   participants that have their hand raised
+ * The action used to determine if the raise hand action is supported by the calling application and
+ * notify the calling application when the user requests to raise or lower their hand.
  */
-// TODO: Refactor to Public API
-@RequiresApi(Build.VERSION_CODES.O)
 @ExperimentalAppActions
-internal class RaiseHandAction(
-    private val participants: StateFlow<Set<Participant>>,
-    private val onRaisedHandsChanged: suspend (Set<Participant>) -> Unit
-) {
-    companion object {
-        const val TAG = CallExtensionsScope.TAG + "(RHCA)"
-    }
-
+public interface RaiseHandAction {
     /**
-     * Whether or not raising/lowering hands is supported by the remote.
+     * Whether or not raising/lowering hands is supported by the calling application.
      *
-     * if `true`, then updates about raised hands will be notified. If `false`, then the remote
-     * doesn't support this action this state will not be notified to the caller.
+     * if `true`, then updates about raised hands from the calling application will be notified. If
+     * `false`, then the calling application doesn't support this action and state changes will not
+     * be notified to the caller and [requestRaisedHandStateChange] requests will fail.
      *
-     * Should not be queried until [CallExtensionsScope.onConnected] is called.
+     * Must not be queried until [CallExtensionScope.onConnected] is called or an error will be
+     * thrown.
      */
-    var isSupported by Delegates.notNull<Boolean>()
-
-    // Contains the remote Binder interface used to notify the remote application of events
-    private var remoteActions: ParticipantActionsRemote? = null
-    // Contains the current state of participants that have their hands raised
-    private val raisedHandState = MutableStateFlow<Set<Int>>(emptySet())
+    public var isSupported: Boolean
 
     /**
-     * Request the remote application to raise or lower this user's hand.
+     * Request the calling application to raise or lower this user's hand.
      *
-     * Note: This operation succeeding does not mean that the raised hand state of the user has
-     * changed. It only means that the request was received by the remote application.
+     * Whether or not this user's hand is currently raised is determined by inspecting whether or
+     * not this [Participant] is currently included in the
+     * [ParticipantExtensionRemote.addRaiseHandAction] `onRaisedHandsChanged` callback `List`.
+     *
+     * Note: A [CallControlResult.Success] result does not mean that the raised hand state of the
+     * user has changed. It only means that the request was received by the remote application and
+     * processed. This can be used to gray out UI until the request has processed.
      *
      * @param isRaised `true` if this user has raised their hand, `false` if they have lowered their
      *   hand
      * @return Whether or not the remote application received this event. This does not mean that
-     *   the operation succeeded, but rather the remote received the event successfully.
+     *   the operation succeeded, but rather the remote received and processed the event
+     *   successfully.
      */
-    suspend fun requestRaisedHandStateChange(isRaised: Boolean): CallControlResult {
-        Log.d(TAG, "setRaisedHandState: isRaised=$isRaised")
-        if (remoteActions == null) {
-            Log.w(TAG, "setRaisedHandState: no binder, isSupported=$isSupported")
-            // TODO: This needs to have its own CallException result
-            return CallControlResult.Error(CallException.ERROR_UNKNOWN)
-        }
-        val cb = ActionsResultCallback()
-        remoteActions?.setHandRaised(isRaised, cb)
-        val result = cb.waitForResponse()
-        Log.d(TAG, "setRaisedHandState: isRaised=$isRaised, result=$result")
-        return result
-    }
-
-    /** Called when the remote application has changed the raised hands state */
-    private suspend fun raisedHandsStateChanged(raisedHands: Set<Int>) {
-        Log.d(TAG, "raisedHandsStateChanged to $raisedHands")
-        raisedHandState.emit(raisedHands)
-    }
-
-    /** Called when capability exchange has completed and we should setup the action */
-    internal fun initialize(
-        callScope: CoroutineScope,
-        isSupported: Boolean,
-        callbacks: ParticipantStateCallbackRepository
-    ) {
-        Log.d(TAG, "initialize, isSupported=$isSupported")
-        this.isSupported = isSupported
-        if (!isSupported) return
-        callbacks.raisedHandsStateCallback = ::raisedHandsStateChanged
-        participants
-            .combine(raisedHandState) { p, rhs -> p.filter { rhs.contains(it.id) } }
-            .distinctUntilChanged()
-            .onEach { filtered -> onRaisedHandsChanged(filtered.toSet()) }
-            .onCompletion { Log.d(TAG, "raised hands flow complete") }
-            .launchIn(callScope)
-    }
-
-    /** Called when the remote has connected for Actions and events are available */
-    internal fun connect(remote: ParticipantActionsRemote?) {
-        Log.d(TAG, "connect: remote is null=${remote == null}")
-        remoteActions = remote
-    }
+    public suspend fun requestRaisedHandStateChange(isRaised: Boolean): CallControlResult
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandActionImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandActionImpl.kt
new file mode 100644
index 0000000..3523081
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandActionImpl.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
+import androidx.core.telecom.CallException
+import androidx.core.telecom.internal.ParticipantActionsRemote
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlin.properties.Delegates
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Implements the ability for the user to raise/lower their hand as well as allow the user to listen
+ * to the hand raised states of all other participants
+ *
+ * @param participants The StateFlow containing the current set of participants in the call at any
+ *   given time.
+ * @param onRaisedHandsChanged The callback that allows the user to listen to the state of
+ *   participants that have their hand raised
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalAppActions::class)
+internal class RaiseHandActionImpl(
+    private val participants: StateFlow<Set<Participant>>,
+    private val onRaisedHandsChanged: suspend (List<Participant>) -> Unit
+) : RaiseHandAction {
+    companion object {
+        const val TAG = CallExtensionScopeImpl.TAG + "(RHCA)"
+    }
+
+    // Contains the remote Binder interface used to notify the remote application of events
+    private var remoteActions: ParticipantActionsRemote? = null
+    // Contains the current state of participant ids that have their hands raised
+    private val raisedHandIdsState = MutableStateFlow<List<String>>(emptyList())
+
+    override var isSupported by Delegates.notNull<Boolean>()
+
+    override suspend fun requestRaisedHandStateChange(isRaised: Boolean): CallControlResult {
+        Log.d(TAG, "setRaisedHandState: isRaised=$isRaised")
+        if (remoteActions == null) {
+            Log.w(TAG, "setRaisedHandState: no binder, isSupported=$isSupported")
+            // TODO: This needs to have its own CallException result
+            return CallControlResult.Error(CallException.ERROR_UNKNOWN)
+        }
+        val cb = ActionsResultCallback()
+        remoteActions?.setHandRaised(isRaised, cb)
+        val result = cb.waitForResponse()
+        Log.d(TAG, "setRaisedHandState: isRaised=$isRaised, result=$result")
+        return result
+    }
+
+    /** Called when the remote application has changed the raised hands state */
+    private suspend fun raisedHandIdsStateChanged(raisedHands: List<String>) {
+        Log.d(TAG, "raisedHandsStateChanged to $raisedHands")
+        raisedHandIdsState.emit(raisedHands)
+    }
+
+    /** Called when capability exchange has completed and we should setup the action */
+    internal fun initialize(
+        callScope: CoroutineScope,
+        isSupported: Boolean,
+        callbacks: ParticipantStateCallbackRepository
+    ) {
+        Log.d(TAG, "initialize, isSupported=$isSupported")
+        this.isSupported = isSupported
+        if (!isSupported) return
+        callbacks.raisedHandIdsStateCallback = ::raisedHandIdsStateChanged
+        participants
+            .combine(raisedHandIdsState) { p, rhs ->
+                rhs.mapNotNull { rh -> p.firstOrNull { it.id == rh } }
+            }
+            .distinctUntilChanged()
+            .onEach { filtered -> onRaisedHandsChanged(filtered) }
+            .onCompletion { Log.d(TAG, "raised hands flow complete") }
+            .launchIn(callScope)
+    }
+
+    /** Called when the remote has connected for Actions and events are available */
+    internal fun connect(remote: ParticipantActionsRemote?) {
+        Log.d(TAG, "connect: remote is null=${remote == null}")
+        remoteActions = remote
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandState.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandState.kt
index 2ef6793..48252b2 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandState.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandState.kt
@@ -16,81 +16,23 @@
 
 package androidx.core.telecom.extensions
 
-import android.util.Log
-import androidx.core.telecom.internal.ParticipantActionCallbackRepository
-import androidx.core.telecom.internal.ParticipantStateListenerRemote
 import androidx.core.telecom.util.ExperimentalAppActions
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 
 /**
- * Tracks the current raised hand state of all of the Participants of this call and notifies the
- * listener if a remote requests to change the user's raised hand state.
- *
- * @param participants The StateFlow containing the current set of Participants in the call
- * @param onHandRaisedChanged The action to perform when the remote InCallService requests to change
- *   this user's raised hand state.
+ * Provides this application with the ability to notify remote surfaces (automotive, watch, etc..)
+ * when [Participant]s in the call have raised or lowered their hands.
  */
 @ExperimentalAppActions
-internal class RaiseHandState(
-    val participants: StateFlow<Set<Participant>>,
-    private val onHandRaisedChanged: suspend (Boolean) -> Unit
-) {
-    companion object {
-        const val LOG_TAG = Extensions.LOG_TAG + "(RHAR)"
-    }
-
-    private val raisedHandsState: MutableStateFlow<Set<Participant>> = MutableStateFlow(emptySet())
-
+public interface RaiseHandState {
     /**
-     * Notify the remote InCallService of an update to the participants that have their hands raised
+     * Notify the remote surfaces of an update to the [Participant]s that have their hands raised at
+     * the current time. Any [Participant] that is in the call and is in [raisedHands] is considered
+     * to have their hand raised and any [Participant] that is in the call that is not in
+     * [raisedHands] is considered to have their hand lowered. The order of the [Participant]s in
+     * [raisedHands] MUST be in the order that the hands were raised, earliest raised hand first.
      *
-     * @param raisedHands The new set of Participants that have their hands raised.
+     * @param raisedHands The updated List of [Participant]s that have their hands raised, ordered
+     *   as earliest raised hand to newest raised hand.
      */
-    suspend fun updateRaisedHands(raisedHands: Set<Participant>) {
-        raisedHandsState.emit(raisedHands)
-    }
-
-    /**
-     * Connect this Action to a new remote that supports listening to this action's state updates.
-     *
-     * @param scope The CoroutineScope to use to update the remote
-     * @param repository The event repository used to listen to state updates from the remote.
-     * @param remote The interface used to communicate with the remote.
-     */
-    internal fun connect(
-        scope: CoroutineScope,
-        repository: ParticipantActionCallbackRepository,
-        remote: ParticipantStateListenerRemote
-    ) {
-        Log.i(LOG_TAG, "initialize: sync state")
-        repository.raiseHandStateCallback = ::raiseHandStateChanged
-        // Send current state
-        remote.updateRaisedHandsAction(raisedHandsState.value.map { it.id }.toIntArray())
-        // Set up updates to the remote when the state changes
-        participants
-            .combine(raisedHandsState) { p, rhs -> p.intersect(rhs) }
-            .distinctUntilChanged()
-            .onEach {
-                Log.i(LOG_TAG, "to remote: updateRaisedHands=$it")
-                remote.updateRaisedHandsAction(it.map { p -> p.id }.toIntArray())
-            }
-            .launchIn(scope)
-    }
-
-    /**
-     * Registered to be called when the remote InCallService has requested to change the raised hand
-     * state of the user.
-     *
-     * @param state The new raised hand state, true if hand is raised, false if it is not.
-     */
-    private suspend fun raiseHandStateChanged(state: Boolean) {
-        Log.d(LOG_TAG, "raisedHandStateChanged: updated state: $state")
-        onHandRaisedChanged(state)
-    }
+    public suspend fun updateRaisedHands(raisedHands: List<Participant>)
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandStateImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandStateImpl.kt
new file mode 100644
index 0000000..2c6cf4c
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/RaiseHandStateImpl.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions
+
+import android.util.Log
+import androidx.core.telecom.internal.ParticipantActionCallbackRepository
+import androidx.core.telecom.internal.ParticipantStateListenerRemote
+import androidx.core.telecom.util.ExperimentalAppActions
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Tracks the current raised hand state of all of the Participants of this call and notifies the
+ * listener if a remote requests to change the user's raised hand state.
+ *
+ * @param participants The StateFlow containing the current set of Participants in the call
+ * @param initialRaisedHands The initial List of [Participant]s that have their hands raised in
+ *   priority order from first raised to last raised.
+ * @param onHandRaisedChanged The action to perform when the remote InCallService requests to change
+ *   this user's raised hand state.
+ */
+@OptIn(ExperimentalAppActions::class)
+internal class RaiseHandStateImpl(
+    private val participants: StateFlow<Set<Participant>>,
+    initialRaisedHands: List<Participant>,
+    private val onHandRaisedChanged: suspend (Boolean) -> Unit
+) : RaiseHandState {
+    companion object {
+        const val LOG_TAG = Extensions.LOG_TAG + "(RHSI)"
+    }
+
+    private val raisedHandsState: MutableStateFlow<List<Participant>> =
+        MutableStateFlow(initialRaisedHands)
+
+    override suspend fun updateRaisedHands(raisedHands: List<Participant>) {
+        raisedHandsState.emit(raisedHands)
+    }
+
+    /**
+     * Connect this Action to a new remote that supports listening to this action's state updates.
+     *
+     * @param scope The CoroutineScope to use to update the remote
+     * @param repository The event repository used to listen to state updates from the remote.
+     * @param remote The interface used to communicate with the remote.
+     */
+    internal fun connect(
+        scope: CoroutineScope,
+        repository: ParticipantActionCallbackRepository,
+        remote: ParticipantStateListenerRemote
+    ) {
+        Log.i(LOG_TAG, "initialize: sync state")
+        repository.raiseHandStateCallback = ::raiseHandStateChanged
+        // Send current state
+        remote.updateRaisedHandsAction(raisedHandsState.value)
+        // Set up updates to the remote when the state changes
+        participants
+            .combine(raisedHandsState) { p, rhs -> rhs.filter { it in p } }
+            .distinctUntilChanged()
+            .onEach {
+                Log.i(LOG_TAG, "to remote: updateRaisedHands=$it")
+                remote.updateRaisedHandsAction(it)
+            }
+            .launchIn(scope)
+    }
+
+    /**
+     * Registered to be called when the remote InCallService has requested to change the raised hand
+     * state of the user.
+     *
+     * @param state The new raised hand state, true if hand is raised, false if it is not.
+     */
+    private suspend fun raiseHandStateChanged(state: Boolean) {
+        Log.d(LOG_TAG, "raisedHandStateChanged: updated state: $state")
+        onHandRaisedChanged(state)
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/AidlExtensions.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/AidlExtensions.kt
index fab3151..a08a7a0 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/AidlExtensions.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/AidlExtensions.kt
@@ -25,7 +25,7 @@
 import androidx.core.telecom.extensions.IParticipantActions
 import androidx.core.telecom.extensions.IParticipantStateListener
 import androidx.core.telecom.extensions.Participant
-import androidx.core.telecom.extensions.ParticipantExtension
+import androidx.core.telecom.extensions.ParticipantParcelable
 import androidx.core.telecom.util.ExperimentalAppActions
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
@@ -52,8 +52,11 @@
      */
     var raiseHandStateCallback: (suspend (Boolean) -> Unit)? = null
 
-    /** The callback that is called when the remote InCallService requests to kick a participant. */
-    var kickParticipantCallback: (suspend (Participant) -> Unit)? = null
+    /**
+     * The callback that is called when the remote InCallService requests to kick a participant
+     * using its id.
+     */
+    var kickParticipantCallback: (suspend (String) -> Unit)? = null
 
     /** Listener used to handle event callbacks from the remote. */
     val eventListener =
@@ -68,11 +71,11 @@
                 }
             }
 
-            override fun kickParticipant(participant: Participant, cb: IActionsResultCallback?) {
+            override fun kickParticipant(participantId: String, cb: IActionsResultCallback?) {
                 cb?.let {
                     coroutineScope.launch {
-                        Log.i(LOG_TAG, "from remote: kickParticipant=$participant")
-                        kickParticipantCallback?.invoke(participant)
+                        Log.i(LOG_TAG, "from remote: kickParticipant=$participantId")
+                        kickParticipantCallback?.invoke(participantId)
                         ActionsResultCallbackRemote(cb).onSuccess()
                     }
                 }
@@ -83,7 +86,11 @@
 /** Remote interface used by InCallServices to send action events to the VOIP application. */
 @ExperimentalAppActions
 internal class ParticipantActionsRemote(binder: IParticipantActions) :
-    IParticipantActions by binder
+    IParticipantActions by binder {
+    fun kickParticipant(participant: Participant, cb: IActionsResultCallback?) {
+        kickParticipant(participant.id, cb)
+    }
+}
 
 /**
  * Remote interface used to notify the ICS of participant state information
@@ -91,8 +98,25 @@
  * @param binder The remote binder interface to wrap
  */
 @ExperimentalAppActions
-internal class ParticipantStateListenerRemote(binder: IParticipantStateListener) :
-    IParticipantStateListener by binder
+internal class ParticipantStateListenerRemote(private val binder: IParticipantStateListener) {
+    fun updateParticipants(participants: Set<Participant>) {
+        binder.updateParticipants(
+            participants.map(Participant::toParticipantParcelable).toTypedArray()
+        )
+    }
+
+    fun updateActiveParticipant(activeParticipant: Participant?) {
+        binder.updateActiveParticipant(activeParticipant?.id)
+    }
+
+    fun updateRaisedHandsAction(participants: List<Participant>) {
+        binder.updateRaisedHandsAction(participants.map { it.id }.toTypedArray())
+    }
+
+    fun finishSync(actions: IParticipantActions) {
+        binder.finishSync(actions)
+    }
+}
 
 /**
  * The remote interface used to begin capability exchange with the InCallService.
@@ -117,24 +141,22 @@
 @ExperimentalAppActions
 internal class ParticipantStateListener(
     private val updateParticipants: (Set<Participant>) -> Unit,
-    private val updateActiveParticipant: (Int?) -> Unit,
-    private val updateRaisedHands: (Set<Int>) -> Unit,
+    private val updateActiveParticipantId: (String?) -> Unit,
+    private val updateRaisedHandIds: (List<String>) -> Unit,
     private val finishSync: (ParticipantActionsRemote?) -> Unit
 ) : IParticipantStateListener.Stub() {
-    override fun updateParticipants(participants: Array<out Participant>?) {
-        updateParticipants.invoke(participants?.toSet() ?: emptySet())
+    override fun updateParticipants(participants: Array<out ParticipantParcelable>?) {
+        updateParticipants.invoke(
+            participants?.map { Participant(it.id, it.name) }?.toSet() ?: emptySet()
+        )
     }
 
-    override fun updateActiveParticipant(activeParticipant: Int) {
-        if (activeParticipant < 0) {
-            updateActiveParticipant.invoke(null)
-        } else {
-            updateActiveParticipant.invoke(activeParticipant)
-        }
+    override fun updateActiveParticipant(activeParticipantId: String?) {
+        updateActiveParticipantId.invoke(activeParticipantId)
     }
 
-    override fun updateRaisedHandsAction(participants: IntArray?) {
-        updateRaisedHands.invoke(participants?.toSet() ?: emptySet())
+    override fun updateRaisedHandsAction(participants: Array<out String>?) {
+        updateRaisedHandIds.invoke(participants?.toList() ?: emptyList())
     }
 
     override fun finishSync(cb: IParticipantActions?) {
@@ -154,12 +176,9 @@
  *   torn down.
  */
 @ExperimentalAppActions
-internal class CapabilityExchangeRepository(connectionScope: CoroutineScope) {
-    companion object {
-        private const val LOG_TAG = Extensions.LOG_TAG + "(CER)"
-    }
+internal class CapabilityExchangeRepository(private val connectionScope: CoroutineScope) {
 
-    /** A request to create the [ParticipantExtension] has been received */
+    /** A request to create the ParticipantExtension has been received */
     var onCreateParticipantExtension:
         ((CoroutineScope, Set<Int>, ParticipantStateListenerRemote) -> Unit)? =
         null
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
index 8777b6c..aaebe36 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
@@ -56,6 +56,7 @@
     val onDisconnectCallback: suspend (disconnectCause: DisconnectCause) -> Unit,
     val onSetActiveCallback: suspend () -> Unit,
     val onSetInactiveCallback: suspend () -> Unit,
+    private val preCallEndpointMapping: PreCallEndpoints? = null,
     private val callChannels: CallChannels,
     private val onEventCallback: suspend (event: String, extras: Bundle) -> Unit,
     private val blockingSessionExecution: CompletableDeferred<Unit>
@@ -70,17 +71,22 @@
     private val mIsCurrentEndpointSet = CompletableDeferred<Unit>()
     private val mIsAvailableEndpointsSet = CompletableDeferred<Unit>()
     private val mIsCurrentlyDisplayingVideo = attributes.isVideoCall()
+    internal val mJetpackToPlatformCallEndpoint: HashMap<ParcelUuid, CallEndpoint> = HashMap()
 
     companion object {
         private val TAG: String = CallSession::class.java.simpleName
         private const val WAIT_FOR_BT_TO_CONNECT_TIMEOUT: Long = 1000L
         private const val SWITCH_TO_SPEAKER_TIMEOUT: Long = WAIT_FOR_BT_TO_CONNECT_TIMEOUT + 1000L
+        private const val INITIAL_ENDPOINT_SWITCH_TIMEOUT: Long = 3000L
+        private const val DELAY_INITIAL_ENDPOINT_SWITCH: Long = 1000L
     }
 
+    @VisibleForTesting
     fun getIsCurrentEndpointSet(): CompletableDeferred<Unit> {
         return mIsCurrentEndpointSet
     }
 
+    @VisibleForTesting
     fun getIsAvailableEndpointsSet(): CompletableDeferred<Unit> {
         return mIsAvailableEndpointsSet
     }
@@ -95,25 +101,67 @@
         mAvailableEndpoints = endpoints
     }
 
+    /**
+     * =========================================================================================
+     * Audio Updates
+     * =========================================================================================
+     */
+    @VisibleForTesting
+    internal fun toRemappedCallEndpointCompat(platformEndpoint: CallEndpoint): CallEndpointCompat {
+        if (platformEndpoint.endpointType == CallEndpoint.TYPE_BLUETOOTH) {
+            val key = platformEndpoint.endpointName
+            val btEndpointMapping = preCallEndpointMapping?.mBluetoothEndpoints
+            return if (btEndpointMapping != null && btEndpointMapping.containsKey(key)) {
+                val existingEndpoint = btEndpointMapping[key]!!
+                mJetpackToPlatformCallEndpoint[existingEndpoint.identifier] = platformEndpoint
+                existingEndpoint
+            } else {
+                EndpointUtils.Api34PlusImpl.toCallEndpointCompat(platformEndpoint)
+            }
+        } else {
+            val key = platformEndpoint.endpointType
+            val nonBtEndpointMapping = preCallEndpointMapping?.mNonBluetoothEndpoints
+            return if (nonBtEndpointMapping != null && nonBtEndpointMapping.containsKey(key)) {
+                val existingEndpoint = nonBtEndpointMapping[key]!!
+                mJetpackToPlatformCallEndpoint[existingEndpoint.identifier] = platformEndpoint
+                existingEndpoint
+            } else {
+                EndpointUtils.Api34PlusImpl.toCallEndpointCompat(platformEndpoint)
+            }
+        }
+    }
+
     override fun onCallEndpointChanged(endpoint: CallEndpoint) {
+        // cache the previous call endpoint for maybeSwitchToSpeakerOnHeadsetDisconnect. This
+        // is used to determine if the last endpoint was BT and the new endpoint is EARPIECE.
         val previousCallEndpoint = mCurrentCallEndpoint
-        mCurrentCallEndpoint = EndpointUtils.Api34PlusImpl.toCallEndpointCompat(endpoint)
+        // due to the [CallsManager#getAvailableStartingCallEndpoints] API, endpoints the client
+        // has can be different from the ones coming from the platform. Hence, a remapping is needed
+        mCurrentCallEndpoint = toRemappedCallEndpointCompat(endpoint)
+        // send the current call endpoint out to the client
         callChannels.currentEndpointChannel.trySend(mCurrentCallEndpoint!!).getOrThrow()
         Log.i(TAG, "onCallEndpointChanged: endpoint=[$endpoint]")
+        // maybeSwitchToSpeakerOnCallStart needs to know when the initial current endpoint is set
         if (!mIsCurrentEndpointSet.isCompleted) {
             mIsCurrentEndpointSet.complete(Unit)
             Log.i(TAG, "onCallEndpointChanged: mCurrentCallEndpoint was set")
         }
         maybeSwitchToSpeakerOnHeadsetDisconnect(mCurrentCallEndpoint!!, previousCallEndpoint)
         // clear out the last user requested CallEndpoint. It's only used to determine if the
-        // change in current endpoints was intentional.
-        mLastClientRequestedEndpoint = null
+        // change in current endpoints was intentional for maybeSwitchToSpeakerOnHeadsetDisconnect
+        if (mLastClientRequestedEndpoint?.type == endpoint.endpointType) {
+            mLastClientRequestedEndpoint = null
+        }
     }
 
     override fun onAvailableCallEndpointsChanged(endpoints: List<CallEndpoint>) {
-        mAvailableEndpoints = EndpointUtils.Api34PlusImpl.toCallEndpointsCompat(endpoints)
+        // due to the [CallsManager#getAvailableStartingCallEndpoints] API, endpoints the client
+        // has can be different from the ones coming from the platform. Hence, a remapping is needed
+        mAvailableEndpoints = endpoints.map { toRemappedCallEndpointCompat(it) }
+        // send the current call endpoints out to the client
         callChannels.availableEndpointChannel.trySend(mAvailableEndpoints).getOrThrow()
         Log.i(TAG, "onAvailableCallEndpointsChanged: endpoints=[$endpoints]")
+        // maybeSwitchToSpeakerOnCallStart needs to know when the initial current endpoints are set
         if (!mIsAvailableEndpointsSet.isCompleted) {
             mIsAvailableEndpointsSet.complete(Unit)
             Log.i(TAG, "onAvailableCallEndpointsChanged: mAvailableEndpoints was set")
@@ -124,13 +172,16 @@
         callChannels.isMutedChannel.trySend(isMuted).getOrThrow()
     }
 
-    override fun onCallStreamingFailed(reason: Int) {
-        TODO("Implement with the CallStreaming code")
-    }
-
-    override fun onEvent(event: String, extras: Bundle) {
-        Log.i(TAG, "onEvent: received $event")
-        CoroutineScope(coroutineContext).launch { onEventCallback(event, extras) }
+    /**
+     * This function should only be run once at the start of CallSession to determine if the
+     * starting CallEndpointCompat should be switched based on the call properties or user request.
+     */
+    suspend fun maybeSwitchStartingEndpoint(preferredStartingCallEndpoint: CallEndpointCompat?) {
+        if (preferredStartingCallEndpoint != null) {
+            switchStartingCallEndpointOnCallStart(preferredStartingCallEndpoint)
+        } else {
+            maybeSwitchToSpeakerOnCallStart()
+        }
     }
 
     /**
@@ -173,7 +224,7 @@
             // only switch to speaker if BT did not connect
             if (!isBluetoothConnected()) {
                 Log.i(TAG, "maybeDelaySwitchToSpeaker: BT did not connect in time!")
-                switchToEndpoint(speakerCompat)
+                requestEndpointChange(speakerCompat)
                 return true
             }
             Log.i(TAG, "maybeDelaySwitchToSpeaker: BT connected! voiding speaker switch.")
@@ -182,7 +233,7 @@
             // otherwise, immediately change from earpiece to speaker because the platform is
             // not in the process of connecting a BT device.
             Log.i(TAG, "maybeDelaySwitchToSpeaker: no BT route available.")
-            switchToEndpoint(speakerCompat)
+            requestEndpointChange(speakerCompat)
             return true
         }
     }
@@ -192,12 +243,27 @@
             mCurrentCallEndpoint!!.type == CallEndpoint.TYPE_BLUETOOTH
     }
 
-    private fun switchToEndpoint(endpoint: CallEndpointCompat) {
-        mPlatformInterface?.requestCallEndpointChange(
-            EndpointUtils.Api34PlusImpl.toCallEndpoint(endpoint),
-            Runnable::run,
-            {}
-        )
+    suspend fun switchStartingCallEndpointOnCallStart(startingCallEndpoint: CallEndpointCompat) {
+        try {
+            withTimeout(INITIAL_ENDPOINT_SWITCH_TIMEOUT) {
+                Log.i(TAG, "switchStartingCallEndpointOnCallStart: before awaitAll")
+                awaitAll(mIsAvailableEndpointsSet)
+                Log.i(TAG, "switchStartingCallEndpointOnCallStart: after awaitAll")
+                launch {
+                    // Delay the switch to a new [CallEndpointCompat] if there is a BT device
+                    // because the request will be overridden once the BT device connects!
+                    if (mAvailableEndpoints.any { it.isBluetoothType() }) {
+                        Log.i(TAG, "switchStartingCallEndpointOnCallStart: BT delay START")
+                        delay(DELAY_INITIAL_ENDPOINT_SWITCH)
+                        Log.i(TAG, "switchStartingCallEndpointOnCallStart: BT delay END")
+                    }
+                    val res = requestEndpointChange(startingCallEndpoint)
+                    Log.i(TAG, "switchStartingCallEndpointOnCallStart: result=$res")
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "switchStartingCallEndpointOnCallStart: hit exception=[$e]")
+        }
     }
 
     /**
@@ -240,6 +306,26 @@
     }
 
     /**
+     * =========================================================================================
+     * Call Event Updates
+     * =========================================================================================
+     */
+    override fun onCallStreamingFailed(reason: Int) {
+        TODO("Implement with the CallStreaming code")
+    }
+
+    override fun onEvent(event: String, extras: Bundle) {
+        Log.i(TAG, "onEvent: received $event")
+        CoroutineScope(coroutineContext).launch { onEventCallback(event, extras) }
+    }
+
+    /**
+     * =========================================================================================
+     * CallControl
+     * =========================================================================================
+     */
+
+    /**
      * CallControl is set by CallsManager#addCall when the CallControl object is returned by the
      * platform
      */
@@ -290,19 +376,34 @@
         return result.getCompleted()
     }
 
-    suspend fun requestEndpointChange(endpoint: CallEndpoint): CallControlResult {
-        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
+    suspend fun requestEndpointChange(endpoint: CallEndpointCompat): CallControlResult {
+        val job: CompletableDeferred<CallControlResult> = CompletableDeferred()
         // cache the last CallEndpoint the user requested to reference in
         // onCurrentCallEndpointChanged. This is helpful for determining if the user intentionally
         // requested a CallEndpoint switch or a headset was disconnected ...
-        mLastClientRequestedEndpoint = EndpointUtils.Api34PlusImpl.toCallEndpointCompat(endpoint)
-        mPlatformInterface?.requestCallEndpointChange(
-            endpoint,
+        mLastClientRequestedEndpoint = endpoint
+        val potentiallyRemappedEndpoint: CallEndpoint =
+            if (mJetpackToPlatformCallEndpoint.containsKey(endpoint.identifier)) {
+                mJetpackToPlatformCallEndpoint[endpoint.identifier]!!
+            } else {
+                EndpointUtils.Api34PlusImpl.toCallEndpoint(endpoint)
+            }
+
+        if (mPlatformInterface == null) {
+            return CallControlResult.Error(androidx.core.telecom.CallException.ERROR_UNKNOWN)
+        }
+
+        mPlatformInterface!!.requestCallEndpointChange(
+            potentiallyRemappedEndpoint,
             Runnable::run,
-            CallControlReceiver(result)
+            CallControlReceiver(job)
         )
-        result.await()
-        return result.getCompleted()
+        job.await()
+        val platformResult = job.getCompleted()
+        if (platformResult != CallControlResult.Success()) {
+            mLastClientRequestedEndpoint = null
+        }
+        return platformResult
     }
 
     suspend fun disconnect(disconnectCause: DisconnectCause): CallControlResult {
@@ -409,9 +510,7 @@
         override suspend fun requestEndpointChange(
             endpoint: CallEndpointCompat
         ): CallControlResult {
-            return session.requestEndpointChange(
-                EndpointUtils.Api34PlusImpl.toCallEndpoint(endpoint)
-            )
+            return session.requestEndpointChange(endpoint)
         }
 
         // Send these events out to the client to collect
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
index b450a38..ec7f0c6 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
@@ -38,6 +38,8 @@
 import androidx.core.telecom.internal.utils.EndpointUtils.Companion.isBluetoothAvailable
 import androidx.core.telecom.internal.utils.EndpointUtils.Companion.isEarpieceEndpoint
 import androidx.core.telecom.internal.utils.EndpointUtils.Companion.isWiredHeadsetOrBtEndpoint
+import androidx.core.telecom.internal.utils.EndpointUtils.Companion.toCallEndpointCompat
+import androidx.core.telecom.internal.utils.EndpointUtils.Companion.toCallEndpointsCompat
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
@@ -57,18 +59,23 @@
     val onSetActiveCallback: suspend () -> Unit,
     val onSetInactiveCallback: suspend () -> Unit,
     val onEventCallback: suspend (event: String, extras: Bundle) -> Unit,
+    private val preferredStartingCallEndpoint: CallEndpointCompat? = null,
+    private val preCallEndpointMapping: PreCallEndpoints? = null,
     private val blockingSessionExecution: CompletableDeferred<Unit>
 ) : android.telecom.Connection() {
     // instance vars
     private val TAG: String = CallSessionLegacy::class.java.simpleName
     private var mCachedBluetoothDevices: ArrayList<BluetoothDevice> = ArrayList()
+    private var mAlreadyRequestedStartingEndpointSwitch: Boolean = false
     private var mAlreadyRequestedSpeaker: Boolean = false
+    private var mPreviousCallEndpoint: CallEndpointCompat? = null
     private var mCurrentCallEndpoint: CallEndpointCompat? = null
+    private var mAvailableCallEndpoints: List<CallEndpointCompat>? = null
     private var mLastClientRequestedEndpoint: CallEndpointCompat? = null
 
     companion object {
-        private val TAG: String = CallSessionLegacy::class.java.simpleName
         private const val WAIT_FOR_BT_TO_CONNECT_TIMEOUT: Long = 1000L
+        private const val DELAY_INITIAL_ENDPOINT_SWITCH: Long = 1000L
         // CallStates. All these states mirror the values in the platform.
         const val STATE_INITIALIZING = 0
         const val STATE_NEW = 1
@@ -106,28 +113,82 @@
      * Audio Updates
      * =========================================================================================
      */
+    @VisibleForTesting
+    internal fun toRemappedCallEndpointCompat(endpoint: CallEndpointCompat): CallEndpointCompat {
+        if (endpoint.isBluetoothType()) {
+            val key = endpoint.name.toString()
+            val btEndpointMapping = preCallEndpointMapping?.mBluetoothEndpoints
+            return if (btEndpointMapping != null && btEndpointMapping.containsKey(key)) {
+                btEndpointMapping[key]!!
+            } else {
+                endpoint
+            }
+        } else {
+            val key = endpoint.type
+            val nonBtEndpointMapping = preCallEndpointMapping?.mNonBluetoothEndpoints
+            return if (nonBtEndpointMapping != null && nonBtEndpointMapping.containsKey(key)) {
+                nonBtEndpointMapping[key]!!
+            } else {
+                endpoint
+            }
+        }
+    }
+
+    private fun setCurrentCallEndpoint(state: CallAudioState) {
+        mPreviousCallEndpoint = mCurrentCallEndpoint
+        mCurrentCallEndpoint = toRemappedCallEndpointCompat(toCallEndpointCompat(state))
+        callChannels.currentEndpointChannel.trySend(mCurrentCallEndpoint!!).getOrThrow()
+    }
+
+    private fun setAvailableCallEndpoints(state: CallAudioState) {
+        val availableEndpoints =
+            toCallEndpointsCompat(state).map { toRemappedCallEndpointCompat(it) }
+        mAvailableCallEndpoints = availableEndpoints
+        callChannels.availableEndpointChannel.trySend(availableEndpoints).getOrThrow()
+    }
+
     override fun onCallAudioStateChanged(state: CallAudioState) {
         if (Build.VERSION.SDK_INT >= VERSION_CODES.P) {
             Api28PlusImpl.refreshBluetoothDeviceCache(mCachedBluetoothDevices, state)
         }
-        val previousCallEndpoint = mCurrentCallEndpoint
-        mCurrentCallEndpoint = EndpointUtils.toCallEndpointCompat(state)
-        callChannels.currentEndpointChannel.trySend(mCurrentCallEndpoint!!).getOrThrow()
-
-        val availableEndpoints = EndpointUtils.toCallEndpointsCompat(state)
-        callChannels.availableEndpointChannel.trySend(availableEndpoints).getOrThrow()
-
+        setCurrentCallEndpoint(state)
+        setAvailableCallEndpoints(state)
         callChannels.isMutedChannel.trySend(state.isMuted).getOrThrow()
-
-        maybeSwitchToSpeakerOnCallStart(mCurrentCallEndpoint!!, availableEndpoints)
+        // On the first call audio state change, determine if the platform started on the correct
+        // audio route.  Otherwise, request an endpoint switch.
+        switchStartingCallEndpointOnCallStart(mAvailableCallEndpoints!!)
+        // In the event the users headset disconnects, they will likely want to continue the call
+        // via the speakerphone
         maybeSwitchToSpeakerOnHeadsetDisconnect(
             mCurrentCallEndpoint!!,
-            previousCallEndpoint,
-            availableEndpoints
+            mPreviousCallEndpoint,
+            mAvailableCallEndpoints!!,
         )
         // clear out the last user requested CallEndpoint. It's only used to determine if the
         // change in current endpoints was intentional.
-        mLastClientRequestedEndpoint = null
+        if (mLastClientRequestedEndpoint?.type == mCurrentCallEndpoint?.type) {
+            mLastClientRequestedEndpoint = null
+        }
+    }
+
+    private fun switchStartingCallEndpointOnCallStart(endpoints: List<CallEndpointCompat>) {
+        if (preferredStartingCallEndpoint != null) {
+            if (!mAlreadyRequestedStartingEndpointSwitch) {
+                CoroutineScope(coroutineContext).launch {
+                    // Delay the switch to a new [CallEndpointCompat] if there is a BT device
+                    // because the request will be overridden once the BT device connects!
+                    if (endpoints.any { it.isBluetoothType() }) {
+                        Log.i(TAG, "switchStartingCallEndpointOnCallStart: BT delay START")
+                        delay(DELAY_INITIAL_ENDPOINT_SWITCH)
+                        Log.i(TAG, "switchStartingCallEndpointOnCallStart: BT delay END")
+                    }
+                    requestEndpointChange(preferredStartingCallEndpoint)
+                }
+            }
+        } else {
+            maybeSwitchToSpeakerOnCallStart(mCurrentCallEndpoint!!, endpoints)
+        }
+        mAlreadyRequestedStartingEndpointSwitch = true
     }
 
     /**
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
index d002d1d..00e5be4 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
@@ -30,6 +30,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresPermission
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.CallsManager
 import androidx.core.telecom.internal.utils.Utils
 import java.util.UUID
@@ -61,6 +62,8 @@
         val onSetActive: suspend () -> Unit,
         val onSetInactive: suspend () -> Unit,
         val onEvent: suspend (event: String, extras: Bundle) -> Unit,
+        val preferredStartingCallEndpoint: CallEndpointCompat? = null,
+        val preCallEndpointMapping: PreCallEndpoints? = null,
         val execution: CompletableDeferred<Unit>
     )
 
@@ -222,6 +225,8 @@
                 targetRequest.onSetActive,
                 targetRequest.onSetInactive,
                 targetRequest.onEvent,
+                targetRequest.preferredStartingCallEndpoint,
+                targetRequest.preCallEndpointMapping,
                 targetRequest.execution
             )
 
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt
new file mode 100644
index 0000000..7f0b519
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.internal
+
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.core.telecom.CallEndpointCompat
+import kotlinx.coroutines.channels.SendChannel
+
+@RequiresApi(Build.VERSION_CODES.O)
+internal class PreCallEndpoints(
+    var mCurrentDevices: MutableList<CallEndpointCompat>,
+    var mSendChannel: SendChannel<List<CallEndpointCompat>>
+) {
+    // earpiece, speaker, unknown, wired_headset
+    val mNonBluetoothEndpoints: HashMap<Int, CallEndpointCompat> = HashMap()
+
+    // all bt endpoints
+    val mBluetoothEndpoints: HashMap<String, CallEndpointCompat> = HashMap()
+
+    companion object {
+        private val TAG: String = PreCallEndpoints::class.java.simpleName.toString()
+
+        // endpoints added constants
+        const val ALREADY_TRACKING_ENDPOINT: Int = 0
+        const val START_TRACKING_NEW_ENDPOINT: Int = 1
+
+        // endpoints removed constants
+        const val NOT_TRACKING_REMOVED_ENDPOINT: Int = 0
+        const val STOP_TRACKING_REMOVED_ENDPOINT: Int = 1
+    }
+
+    init {
+        for (device in mCurrentDevices) {
+            if (device.isBluetoothType()) {
+                mBluetoothEndpoints[device.name.toString()] = device
+            } else {
+                mNonBluetoothEndpoints[device.type] = device
+            }
+        }
+    }
+
+    fun endpointsAddedUpdate(addedCallEndpoints: List<CallEndpointCompat>) {
+        var addedDevicesCount = 0
+        for (maybeNewEndpoint in addedCallEndpoints) {
+            addedDevicesCount += maybeAddCallEndpoint(maybeNewEndpoint)
+        }
+        if (addedDevicesCount > 0) {
+            updateClient()
+        } else {
+            Log.d(TAG, "endpointsAddedUpdate: no new added endpoints, not updating client!")
+        }
+    }
+
+    fun endpointsRemovedUpdate(removedCallEndpoints: List<CallEndpointCompat>) {
+        var removedDevicesCount = 0
+        for (maybeRemovedDevice in removedCallEndpoints) {
+            removedDevicesCount += maybeRemoveCallEndpoint(maybeRemovedDevice)
+        }
+        if (removedDevicesCount > 0) {
+            mCurrentDevices =
+                (mBluetoothEndpoints.values + mNonBluetoothEndpoints.values).toMutableList()
+            updateClient()
+        } else {
+            Log.d(TAG, "endpointsRemovedUpdate: no removed endpoints, not updating client!")
+        }
+    }
+
+    internal fun isCallEndpointBeingTracked(endpoint: CallEndpointCompat?): Boolean {
+        return mCurrentDevices.contains(endpoint)
+    }
+
+    @VisibleForTesting
+    internal fun maybeAddCallEndpoint(endpoint: CallEndpointCompat): Int {
+        if (endpoint.isBluetoothType()) {
+            if (!mBluetoothEndpoints.containsKey(endpoint.name.toString())) {
+                mBluetoothEndpoints[endpoint.name.toString()] = endpoint
+                mCurrentDevices.add(endpoint)
+                return START_TRACKING_NEW_ENDPOINT
+            } else {
+                return ALREADY_TRACKING_ENDPOINT
+            }
+        } else {
+            if (!mNonBluetoothEndpoints.containsKey(endpoint.type)) {
+                mNonBluetoothEndpoints[endpoint.type] = endpoint
+                mCurrentDevices.add(endpoint)
+                return START_TRACKING_NEW_ENDPOINT
+            } else {
+                return ALREADY_TRACKING_ENDPOINT
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal fun maybeRemoveCallEndpoint(endpoint: CallEndpointCompat): Int {
+        if (endpoint.isBluetoothType()) {
+            if (mBluetoothEndpoints.containsKey(endpoint.name.toString())) {
+                mBluetoothEndpoints.remove(endpoint.name.toString())
+                return STOP_TRACKING_REMOVED_ENDPOINT
+            } else {
+                return NOT_TRACKING_REMOVED_ENDPOINT
+            }
+        } else {
+            if (mNonBluetoothEndpoints.containsKey(endpoint.type)) {
+                mNonBluetoothEndpoints.remove(endpoint.type)
+                return STOP_TRACKING_REMOVED_ENDPOINT
+            } else {
+                return NOT_TRACKING_REMOVED_ENDPOINT
+            }
+        }
+    }
+
+    private fun updateClient() {
+        mSendChannel.trySend(mCurrentDevices)
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/AudioManagerUtil.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/AudioManagerUtil.kt
new file mode 100644
index 0000000..2777997
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/AudioManagerUtil.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.internal.utils
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.os.Build
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+
+@RequiresApi(23)
+internal class AudioManagerUtil {
+    companion object {
+        fun getAvailableAudioDevices(audioManager: AudioManager): List<AudioDeviceInfo> {
+            return if (Build.VERSION.SDK_INT >= 31) {
+                AudioManager31PlusImpl.getDevices(audioManager)
+            } else {
+                AudioManager23PlusImpl.getDevices(audioManager)
+            }
+        }
+    }
+
+    @RequiresApi(31)
+    object AudioManager31PlusImpl {
+        @JvmStatic
+        @DoNotInline
+        fun getDevices(audioManager: AudioManager): List<AudioDeviceInfo> {
+            return audioManager.availableCommunicationDevices
+        }
+    }
+
+    @RequiresApi(23)
+    object AudioManager23PlusImpl {
+        @JvmStatic
+        @DoNotInline
+        fun getDevices(audioManager: AudioManager): List<AudioDeviceInfo> {
+            return audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS).toList()
+        }
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
index d2ea85a..4b5cdbd 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
@@ -17,17 +17,113 @@
 package androidx.core.telecom.internal.utils
 
 import android.bluetooth.BluetoothDevice
+import android.content.Context
+import android.media.AudioDeviceInfo
 import android.os.Build
 import android.os.Build.VERSION.SDK_INT
 import android.os.Build.VERSION_CODES.P
+import android.os.ParcelUuid
 import android.telecom.CallAudioState
+import android.util.Log
+import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallEndpointCompat
+import androidx.core.telecom.CallEndpointCompat.Companion.EndpointType
+import androidx.core.telecom.R
+import java.util.UUID
 
 @RequiresApi(Build.VERSION_CODES.O)
 internal class EndpointUtils {
 
     companion object {
+        private val TAG: String = EndpointUtils::class.java.simpleName.toString()
+
+        /** [AudioDeviceInfo]s to [CallEndpointCompat]s */
+        fun getEndpointsFromAudioDeviceInfo(
+            c: Context,
+            adiArr: List<AudioDeviceInfo>?
+        ): List<CallEndpointCompat> {
+            if (adiArr == null) {
+                return listOf()
+            }
+            val endpoints: MutableList<CallEndpointCompat> = mutableListOf()
+            val omittedDevices = StringBuilder("omitting devices =[")
+            adiArr.toList().forEach { audioDeviceInfo ->
+                val endpoint = getEndpointFromAudioDeviceInfo(c, audioDeviceInfo)
+                if (endpoint.type != CallEndpointCompat.TYPE_UNKNOWN) {
+                    endpoints.add(endpoint)
+                } else {
+                    omittedDevices.append(
+                        "(type=[${audioDeviceInfo.type}]," +
+                            " name=[${audioDeviceInfo.productName}]),"
+                    )
+                }
+            }
+            omittedDevices.append("]")
+            Log.i(TAG, omittedDevices.toString())
+            return endpoints
+        }
+
+        /** [AudioDeviceInfo] --> [CallEndpointCompat] */
+        private fun getEndpointFromAudioDeviceInfo(
+            c: Context,
+            adi: AudioDeviceInfo
+        ): CallEndpointCompat {
+            val newEndpoint =
+                CallEndpointCompat(
+                    remapAudioDeviceNameToEndpointName(c, adi),
+                    remapAudioDeviceTypeToCallEndpointType(adi.type),
+                    ParcelUuid(UUID.randomUUID())
+                )
+            if (SDK_INT >= P && newEndpoint.isBluetoothType()) {
+                newEndpoint.mMackAddress = adi.address
+            }
+            return newEndpoint
+        }
+
+        private fun remapAudioDeviceNameToEndpointName(
+            c: Context,
+            audioDeviceInfo: AudioDeviceInfo
+        ): String {
+            return when (audioDeviceInfo.type) {
+                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE ->
+                    c.getString(R.string.callendpoint_name_earpiece)
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER ->
+                    c.getString(R.string.callendpoint_name_speaker)
+                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                AudioDeviceInfo.TYPE_USB_DEVICE,
+                AudioDeviceInfo.TYPE_USB_ACCESSORY,
+                AudioDeviceInfo.TYPE_USB_HEADSET ->
+                    c.getString(R.string.callendpoint_name_wiredheadset)
+                else -> audioDeviceInfo.productName.toString()
+            }
+        }
+
+        internal fun remapAudioDeviceTypeToCallEndpointType(
+            audioDeviceInfoType: Int
+        ): (@EndpointType Int) {
+            return when (audioDeviceInfoType) {
+                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> CallEndpointCompat.TYPE_EARPIECE
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> CallEndpointCompat.TYPE_SPEAKER
+                // Wired Headset Devices
+                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                AudioDeviceInfo.TYPE_USB_DEVICE,
+                AudioDeviceInfo.TYPE_USB_ACCESSORY,
+                AudioDeviceInfo.TYPE_USB_HEADSET -> CallEndpointCompat.TYPE_WIRED_HEADSET
+                // Bluetooth Devices
+                AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                AudioDeviceInfo.TYPE_BLE_BROADCAST -> CallEndpointCompat.TYPE_BLUETOOTH
+                // Everything else is defaulted to TYPE_UNKNOWN
+                else -> CallEndpointCompat.TYPE_UNKNOWN
+            }
+        }
+
         fun getSpeakerEndpoint(endpoints: List<CallEndpointCompat>): CallEndpointCompat? {
             for (e in endpoints) {
                 if (e.type == CallEndpointCompat.TYPE_SPEAKER) {
@@ -82,7 +178,7 @@
                 )
             }
             if (hasBluetoothType(bitMask)) {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                if (SDK_INT >= P) {
                     endpoints.addAll(BluetoothApi28PlusImpl.getBluetoothEndpoints(state))
                 } else {
                     endpoints.add(
@@ -187,17 +283,7 @@
         }
 
         @JvmStatic
-        fun toCallEndpointsCompat(
-            endpoints: List<android.telecom.CallEndpoint>
-        ): List<CallEndpointCompat> {
-            val res = ArrayList<CallEndpointCompat>()
-            for (e in endpoints) {
-                res.add(CallEndpointCompat(e.endpointName, e.endpointType, e.identifier))
-            }
-            return res
-        }
-
-        @JvmStatic
+        @DoNotInline
         fun toCallEndpoint(e: CallEndpointCompat): android.telecom.CallEndpoint {
             return android.telecom.CallEndpoint(e.name, e.type, e.identifier)
         }
diff --git a/core/core-telecom/src/main/res/values/strings.xml b/core/core-telecom/src/main/res/values/strings.xml
new file mode 100644
index 0000000..98e241b
--- /dev/null
+++ b/core/core-telecom/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <!-- The user-visible name of the earpiece type CallEndpoint -->
+    <string name="callendpoint_name_earpiece">Earpiece</string>
+    <!-- The user-visible name of the wired headset type CallEndpoint -->
+    <string name="callendpoint_name_wiredheadset">Wired headset</string>
+    <!-- The user-visible name of the speaker type CallEndpoint -->
+    <string name="callendpoint_name_speaker">Speaker</string>
+</resources>
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 6448a6d..8d7d895 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1850,11 +1850,13 @@
 
 package androidx.core.os {
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public enum BufferFillPolicy {
-    method public int getValue();
-    property public final int value;
-    enum_constant public static final androidx.core.os.BufferFillPolicy DISCARD;
-    enum_constant public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract sealed class BufferFillPolicy {
+    field public static final androidx.core.os.BufferFillPolicy.Companion Companion;
+    field public static final androidx.core.os.BufferFillPolicy DISCARD;
+    field public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
+  }
+
+  public static final class BufferFillPolicy.Companion {
   }
 
   public final class BuildCompat {
@@ -1924,14 +1926,16 @@
     method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+    ctor public HeapProfileRequestBuilder();
     method public androidx.core.os.HeapProfileRequestBuilder setBufferSizeKb(int bufferSizeKb);
     method public androidx.core.os.HeapProfileRequestBuilder setDurationMs(int durationMs);
     method public androidx.core.os.HeapProfileRequestBuilder setSamplingIntervalBytes(long samplingIntervalBytes);
     method public androidx.core.os.HeapProfileRequestBuilder setTrackJavaAllocations(boolean traceJavaAllocations);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+    ctor public JavaHeapDumpRequestBuilder();
     method public androidx.core.os.JavaHeapDumpRequestBuilder setBufferSizeKb(int bufferSizeKb);
   }
 
@@ -1996,26 +2000,36 @@
   public final class Profiling {
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.HeapProfileRequestBuilder requestHeapProfile();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.JavaHeapDumpRequestBuilder requestJavaHeapDump();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.StackSamplingRequestBuilder requestStackSampling();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.SystemTraceRequestBuilder requestSystemTrace();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class RequestBuilder<T extends androidx.core.os.RequestBuilder<T>> {
-    method public final void request(android.content.Context context, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class ProfilingRequest {
+    method public android.os.CancellationSignal? getCancellationSignal();
+    method public android.os.Bundle getParams();
+    method public int getProfilingType();
+    method public String? getTag();
+    property public final android.os.CancellationSignal? cancellationSignal;
+    property public final android.os.Bundle params;
+    property public final int profilingType;
+    property public final String? tag;
+  }
+
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
+    method public final androidx.core.os.ProfilingRequest build();
     method public final T setCancellationSignal(android.os.CancellationSignal cancellationSignal);
     method public final T setTag(String tag);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+    ctor public StackSamplingRequestBuilder();
     method public androidx.core.os.StackSamplingRequestBuilder setBufferSizeKb(int bufferSizeKb);
     method public androidx.core.os.StackSamplingRequestBuilder setDurationMs(int durationMs);
     method public androidx.core.os.StackSamplingRequestBuilder setSamplingFrequencyHz(int samplingFrequencyHz);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+    ctor public SystemTraceRequestBuilder();
     method public androidx.core.os.SystemTraceRequestBuilder setBufferFillPolicy(androidx.core.os.BufferFillPolicy bufferFillPolicy);
     method public androidx.core.os.SystemTraceRequestBuilder setBufferSizeKb(int bufferSizeKb);
     method public androidx.core.os.SystemTraceRequestBuilder setDurationMs(int durationMs);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 7e2492c..c60ec0c 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2243,11 +2243,13 @@
 
 package androidx.core.os {
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public enum BufferFillPolicy {
-    method public int getValue();
-    property public final int value;
-    enum_constant public static final androidx.core.os.BufferFillPolicy DISCARD;
-    enum_constant public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract sealed class BufferFillPolicy {
+    field public static final androidx.core.os.BufferFillPolicy.Companion Companion;
+    field public static final androidx.core.os.BufferFillPolicy DISCARD;
+    field public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
+  }
+
+  public static final class BufferFillPolicy.Companion {
   }
 
   public final class BuildCompat {
@@ -2317,7 +2319,8 @@
     method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+    ctor public HeapProfileRequestBuilder();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected androidx.core.os.HeapProfileRequestBuilder getThis();
@@ -2327,7 +2330,8 @@
     method public androidx.core.os.HeapProfileRequestBuilder setTrackJavaAllocations(boolean traceJavaAllocations);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+    ctor public JavaHeapDumpRequestBuilder();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected androidx.core.os.JavaHeapDumpRequestBuilder getThis();
@@ -2395,23 +2399,32 @@
   public final class Profiling {
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.HeapProfileRequestBuilder requestHeapProfile();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.JavaHeapDumpRequestBuilder requestJavaHeapDump();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.StackSamplingRequestBuilder requestStackSampling();
-    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static androidx.core.os.SystemTraceRequestBuilder requestSystemTrace();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
     method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class RequestBuilder<T extends androidx.core.os.RequestBuilder<T>> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class ProfilingRequest {
+    method public android.os.CancellationSignal? getCancellationSignal();
+    method public android.os.Bundle getParams();
+    method public int getProfilingType();
+    method public String? getTag();
+    property public final android.os.CancellationSignal? cancellationSignal;
+    property public final android.os.Bundle params;
+    property public final int profilingType;
+    property public final String? tag;
+  }
+
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
+    method public final androidx.core.os.ProfilingRequest build();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected abstract android.os.Bundle getParams();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected abstract int getProfilingType();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected abstract T getThis();
-    method public final void request(android.content.Context context, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
     method public final T setCancellationSignal(android.os.CancellationSignal cancellationSignal);
     method public final T setTag(String tag);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+    ctor public StackSamplingRequestBuilder();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected androidx.core.os.StackSamplingRequestBuilder getThis();
@@ -2420,7 +2433,8 @@
     method public androidx.core.os.StackSamplingRequestBuilder setSamplingFrequencyHz(int samplingFrequencyHz);
   }
 
-  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.RequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+  @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+    ctor public SystemTraceRequestBuilder();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected androidx.core.os.SystemTraceRequestBuilder getThis();
diff --git a/core/core/src/androidTest/java/androidx/core/os/ProfilingTest.kt b/core/core/src/androidTest/java/androidx/core/os/ProfilingTest.kt
index 5df8859..30030b5 100644
--- a/core/core/src/androidTest/java/androidx/core/os/ProfilingTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/os/ProfilingTest.kt
@@ -59,13 +59,12 @@
     fun testRequestJavaHeapDump() {
         val listener = ResultListener()
 
-        requestJavaHeapDump()
-            .setBufferSizeKb(1000)
-            .request(
-                ApplicationProvider.getApplicationContext(),
-                Executors.newSingleThreadExecutor(),
-                listener
-            )
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            JavaHeapDumpRequestBuilder().setBufferSizeKb(1000).build(),
+            Executors.newSingleThreadExecutor(),
+            listener
+        )
 
         waitForSignal(listener.mAcceptedSignal)
 
@@ -78,16 +77,17 @@
     fun testRequestHeapProfile() {
         val listener = ResultListener()
 
-        requestHeapProfile()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setTrackJavaAllocations(false)
-            .setSamplingIntervalBytes(100)
-            .request(
-                ApplicationProvider.getApplicationContext(),
-                Executors.newSingleThreadExecutor(),
-                listener
-            )
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            HeapProfileRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setTrackJavaAllocations(false)
+                .setSamplingIntervalBytes(100)
+                .build(),
+            Executors.newSingleThreadExecutor(),
+            listener
+        )
 
         waitForSignal(listener.mAcceptedSignal)
 
@@ -100,15 +100,16 @@
     fun testRequestStackSampling() {
         val listener = ResultListener()
 
-        requestStackSampling()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setSamplingFrequencyHz(100)
-            .request(
-                ApplicationProvider.getApplicationContext(),
-                Executors.newSingleThreadExecutor(),
-                listener
-            )
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            StackSamplingRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setSamplingFrequencyHz(100)
+                .build(),
+            Executors.newSingleThreadExecutor(),
+            listener
+        )
 
         waitForSignal(listener.mAcceptedSignal)
 
@@ -121,15 +122,16 @@
     fun testRequestSystemTrace() {
         val listener = ResultListener()
 
-        requestSystemTrace()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setBufferFillPolicy(BufferFillPolicy.DISCARD)
-            .request(
-                ApplicationProvider.getApplicationContext(),
-                Executors.newSingleThreadExecutor(),
-                listener
-            )
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            SystemTraceRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setBufferFillPolicy(BufferFillPolicy.DISCARD)
+                .build(),
+            Executors.newSingleThreadExecutor(),
+            listener
+        )
 
         waitForSignal(listener.mAcceptedSignal)
 
@@ -146,16 +148,17 @@
         val cancellationSignal = CancellationSignal()
         val listener = ResultListener()
 
-        requestSystemTrace()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5 * 60 * 1000)
-            .setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
-            .setCancellationSignal(cancellationSignal)
-            .request(
-                ApplicationProvider.getApplicationContext(),
-                Executors.newSingleThreadExecutor(),
-                listener
-            )
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            SystemTraceRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5 * 60 * 1000)
+                .setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
+                .setCancellationSignal(cancellationSignal)
+                .build(),
+            Executors.newSingleThreadExecutor(),
+            listener
+        )
 
         // Schedule cancellation to occur after some short wait
         Handler(Looper.getMainLooper())
@@ -187,12 +190,17 @@
             listener2
         )
 
-        requestHeapProfile()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setTrackJavaAllocations(true)
-            .setSamplingIntervalBytes(100)
-            .request(ApplicationProvider.getApplicationContext(), null, null)
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            HeapProfileRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setTrackJavaAllocations(true)
+                .setSamplingIntervalBytes(100)
+                .build(),
+            null,
+            null
+        )
 
         waitForSignal(listener1.mAcceptedSignal)
         waitForSignal(listener2.mAcceptedSignal)
@@ -221,23 +229,33 @@
         // Wait for the other thread to actually register its listener
         runBlocking { delay(1000) }
 
-        requestHeapProfile()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setTrackJavaAllocations(true)
-            .setSamplingIntervalBytes(100)
-            .request(ApplicationProvider.getApplicationContext(), null, null)
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            HeapProfileRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setTrackJavaAllocations(true)
+                .setSamplingIntervalBytes(100)
+                .build(),
+            null,
+            null
+        )
 
         waitForSignal(acceptedSignal)
 
         // Reset the latch
         acceptedSignal = CountDownLatch(1)
 
-        requestStackSampling()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setSamplingFrequencyHz(100)
-            .request(ApplicationProvider.getApplicationContext(), null, null)
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            StackSamplingRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setSamplingFrequencyHz(100)
+                .build(),
+            null,
+            null
+        )
 
         waitForSignal(acceptedSignal)
 
@@ -266,11 +284,17 @@
             listener2
         )
 
-        requestHeapProfile()
-            .setBufferSizeKb(1000)
-            .setDurationMs(5000)
-            .setSamplingIntervalBytes(4096L)
-            .request(ApplicationProvider.getApplicationContext(), null, null)
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            HeapProfileRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(5000)
+                .setSamplingIntervalBytes(4096L)
+                .build(),
+            null,
+            null
+        )
+
         unregisterForAllProfilingResults(ApplicationProvider.getApplicationContext(), listener2)
 
         waitForSignal(listener1.mAcceptedSignal)
@@ -313,12 +337,17 @@
         // Wait for the other thread to actually register its listener
         runBlocking { delay(1000) }
 
-        requestHeapProfile()
-            .setBufferSizeKb(1000)
-            .setDurationMs(10 * 1000)
-            .setTrackJavaAllocations(true)
-            .setSamplingIntervalBytes(100)
-            .request(ApplicationProvider.getApplicationContext(), null, null)
+        requestProfiling(
+            ApplicationProvider.getApplicationContext(),
+            HeapProfileRequestBuilder()
+                .setBufferSizeKb(1000)
+                .setDurationMs(10 * 1000)
+                .setTrackJavaAllocations(true)
+                .setSamplingIntervalBytes(100)
+                .build(),
+            null,
+            null
+        )
 
         // Schedule cancellation to occur after some short wait
         Handler(Looper.getMainLooper()).postDelayed({ scopeToUnregister.cancel() }, 1000L)
diff --git a/core/core/src/main/java/androidx/core/os/Profiling.kt b/core/core/src/main/java/androidx/core/os/Profiling.kt
index eb209bb..55a4c00 100644
--- a/core/core/src/main/java/androidx/core/os/Profiling.kt
+++ b/core/core/src/main/java/androidx/core/os/Profiling.kt
@@ -48,9 +48,18 @@
 // End section: Keep in sync with ProfilingManager
 
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public enum class BufferFillPolicy(public val value: Int) {
-    DISCARD(VALUE_BUFFER_FILL_POLICY_DISCARD),
-    RING_BUFFER(VALUE_BUFFER_FILL_POLICY_RING_BUFFER),
+public sealed class BufferFillPolicy(internal val value: Int) {
+    public companion object {
+        @JvmField @SuppressWarnings("AcronymName") public val DISCARD: BufferFillPolicy = Discard()
+
+        @JvmField
+        @SuppressWarnings("AcronymName")
+        public val RING_BUFFER: BufferFillPolicy = RingBuffer()
+    }
+
+    private class Discard : BufferFillPolicy(VALUE_BUFFER_FILL_POLICY_DISCARD)
+
+    private class RingBuffer : BufferFillPolicy(VALUE_BUFFER_FILL_POLICY_RING_BUFFER)
 }
 
 /** Obtain a flow to be called with all profiling results for this UID. */
@@ -83,49 +92,34 @@
 }
 
 /**
- * Obtain a builder to create a request for a java heap dump from {@link ProfilingManager}.
+ * Request profiling using a {@link ProfilingRequest} generated by one of the provided builders.
  *
- * Request is submitted by calling {@link JavaHeapDumpRequestBuilder#request}.
+ * If the executor and/or listener are null, and if no global listener and executor combinations are
+ * registered using {@link registerForAllProfilingResults}, the request will be dropped.
  */
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public fun requestJavaHeapDump(): JavaHeapDumpRequestBuilder {
-    return JavaHeapDumpRequestBuilder()
-}
-
-/**
- * Obtain a builder to create a request for a heap profile from {@link ProfilingManager}.
- *
- * Request is submitted by calling {@link HeapProfileRequestBuilder#request}.
- */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public fun requestHeapProfile(): HeapProfileRequestBuilder {
-    return HeapProfileRequestBuilder()
-}
-
-/**
- * Obtain a builder to create a request for stack sampling from {@link ProfilingManager}.
- *
- * Request is submitted by calling {@link StackSamplingRequestBuilder#request}.
- */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public fun requestStackSampling(): StackSamplingRequestBuilder {
-    return StackSamplingRequestBuilder()
-}
-
-/**
- * Obtain a builder to create a request for a system trace from {@link ProfilingManager}.
- *
- * Request is submitted by calling {@link SystemTraceRequestBuilder#request}.
- */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public fun requestSystemTrace(): SystemTraceRequestBuilder {
-    return SystemTraceRequestBuilder()
+public fun requestProfiling(
+    context: Context,
+    profilingRequest: ProfilingRequest,
+    executor: Executor?,
+    listener: Consumer<ProfilingResult>?
+) {
+    val service = context.getSystemService(ProfilingManager::class.java)
+    service.requestProfiling(
+        profilingRequest.profilingType,
+        profilingRequest.params,
+        profilingRequest.tag,
+        profilingRequest.cancellationSignal,
+        executor,
+        listener
+    )
 }
 
 /** Base class for request builders. */
-@SuppressWarnings("StaticFinalBuilder", "MissingBuildMethod", "TopLevelBuilder")
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public abstract class RequestBuilder<T : RequestBuilder<T>> internal constructor() {
+@SuppressWarnings("StaticFinalBuilder", "TopLevelBuilder")
+public abstract class ProfilingRequestBuilder<T : ProfilingRequestBuilder<T>>
+internal constructor() {
     private var mTag: String? = null
     private var mCancellationSignal: CancellationSignal? = null
 
@@ -148,26 +142,11 @@
     }
 
     /**
-     * Submit the profiling request with the provided executor and listener.
-     *
-     * If the executor and/or listener are null, and if no global listener and executor combinations
-     * are registered using {@link registerForAllProfilingResults}, the request will be dropped.
+     * Build the {@link ProfilingRequest} object which can be used with {@link requestProfiling} to
+     * request profiling.
      */
-    @SuppressWarnings("BuilderSetStyle")
-    public fun request(
-        context: Context,
-        executor: Executor?,
-        listener: Consumer<ProfilingResult>?
-    ) {
-        val service = context.getSystemService(ProfilingManager::class.java)
-        service.requestProfiling(
-            getProfilingType(),
-            getParams(),
-            mTag,
-            mCancellationSignal,
-            executor,
-            listener
-        )
+    public fun build(): ProfilingRequest {
+        return ProfilingRequest(getProfilingType(), getParams(), mTag, mCancellationSignal)
     }
 
     @SuppressWarnings("HiddenAbstractMethod")
@@ -183,10 +162,9 @@
     protected abstract fun getParams(): Bundle
 }
 
-/** Request builder for a java heap dump. */
+/** Request builder to create a request for a java heap dump from {@link ProfilingManager}. */
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public class JavaHeapDumpRequestBuilder internal constructor() :
-    RequestBuilder<JavaHeapDumpRequestBuilder>() {
+public class JavaHeapDumpRequestBuilder : ProfilingRequestBuilder<JavaHeapDumpRequestBuilder>() {
     private val mParams: Bundle = Bundle()
 
     @RestrictTo(RestrictTo.Scope.SUBCLASSES)
@@ -211,10 +189,9 @@
     }
 }
 
-/** Request builder for a heap profile. */
+/** Request builder to create a request for a heap profile from {@link ProfilingManager}. */
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public class HeapProfileRequestBuilder internal constructor() :
-    RequestBuilder<HeapProfileRequestBuilder>() {
+public class HeapProfileRequestBuilder : ProfilingRequestBuilder<HeapProfileRequestBuilder>() {
     private val mParams: Bundle = Bundle()
 
     @RestrictTo(RestrictTo.Scope.SUBCLASSES)
@@ -257,10 +234,9 @@
     }
 }
 
-/** Request builder for stack sampling. */
+/** Request builder to create a request for stack sampling from {@link ProfilingManager}. */
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public class StackSamplingRequestBuilder internal constructor() :
-    RequestBuilder<StackSamplingRequestBuilder>() {
+public class StackSamplingRequestBuilder : ProfilingRequestBuilder<StackSamplingRequestBuilder>() {
     private val mParams: Bundle = Bundle()
 
     @RestrictTo(RestrictTo.Scope.SUBCLASSES)
@@ -297,10 +273,9 @@
     }
 }
 
-/** Request builder for a system trace. */
+/** Request builder to create a request for a system trace from {@link ProfilingManager}. */
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public class SystemTraceRequestBuilder internal constructor() :
-    RequestBuilder<SystemTraceRequestBuilder>() {
+public class SystemTraceRequestBuilder : ProfilingRequestBuilder<SystemTraceRequestBuilder>() {
     private val mParams: Bundle = Bundle()
 
     @RestrictTo(RestrictTo.Scope.SUBCLASSES)
@@ -336,3 +311,19 @@
         return this
     }
 }
+
+/**
+ * Profiling request class containing data to submit a profiling request.
+ *
+ * This should be constructed using one of the provided builders.
+ *
+ * This should be used with {@link requestProfiling} to submit a profiling request.
+ */
+@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class ProfilingRequest
+internal constructor(
+    public val profilingType: Int,
+    public val params: Bundle,
+    public val tag: String?,
+    public val cancellationSignal: CancellationSignal?
+)
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index 0692467..aba3b90 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -11,8 +11,9 @@
   }
 
   public abstract class CreateCredentialRequest {
-    method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
-    method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(34) public static final androidx.credentials.CreateCredentialRequest createFrom(android.credentials.CreateCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getCredentialData();
     method public final androidx.credentials.CreateCredentialRequest.DisplayInfo getDisplayInfo();
@@ -33,15 +34,16 @@
   }
 
   public static final class CreateCredentialRequest.Companion {
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(34) public androidx.credentials.CreateCredentialRequest createFrom(android.credentials.CreateCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
   }
 
   public static final class CreateCredentialRequest.DisplayInfo {
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId);
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, optional CharSequence? userDisplayName);
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, CharSequence? userDisplayName, String? preferDefaultProvider);
-    method @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
+    method @Discouraged(message="It is recommended to construct a DisplayInfo by directly using the DisplayInfo constructor") @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
     method public CharSequence? getUserDisplayName();
     method public CharSequence getUserId();
     property public final CharSequence? userDisplayName;
@@ -50,7 +52,7 @@
   }
 
   public static final class CreateCredentialRequest.DisplayInfo.Companion {
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
+    method @Discouraged(message="It is recommended to construct a DisplayInfo by directly using the DisplayInfo constructor") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
   }
 
   public abstract class CreateCredentialResponse {
@@ -133,6 +135,8 @@
   }
 
   public abstract class Credential {
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") @RequiresApi(34) public static final androidx.credentials.Credential createFrom(android.credentials.Credential credential);
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") public static final androidx.credentials.Credential createFrom(String type, android.os.Bundle data);
     method public final android.os.Bundle getData();
     method public final String getType();
     property public final android.os.Bundle data;
@@ -141,6 +145,8 @@
   }
 
   public static final class Credential.Companion {
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") @RequiresApi(34) public androidx.credentials.Credential createFrom(android.credentials.Credential credential);
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") public androidx.credentials.Credential createFrom(String type, android.os.Bundle data);
   }
 
   public interface CredentialManager {
@@ -174,6 +180,8 @@
   }
 
   public abstract class CredentialOption {
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") @RequiresApi(34) public static final androidx.credentials.CredentialOption createFrom(android.credentials.CredentialOption option);
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") public static final androidx.credentials.CredentialOption createFrom(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, java.util.Set<android.content.ComponentName> allowedProviders);
     method public final java.util.Set<android.content.ComponentName> getAllowedProviders();
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getRequestData();
@@ -196,6 +204,8 @@
   }
 
   public static final class CredentialOption.Companion {
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") @RequiresApi(34) public androidx.credentials.CredentialOption createFrom(android.credentials.CredentialOption option);
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") public androidx.credentials.CredentialOption createFrom(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, java.util.Set<android.content.ComponentName> allowedProviders);
   }
 
   public interface CredentialProvider {
@@ -217,16 +227,20 @@
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi, optional android.content.ComponentName? preferUiBrandingComponentName);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi, optional android.content.ComponentName? preferUiBrandingComponentName, optional boolean preferImmediatelyAvailableCredentials);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") @RequiresApi(34) public static androidx.credentials.GetCredentialRequest createFrom(android.credentials.GetCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") public static androidx.credentials.GetCredentialRequest createFrom(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, String? origin, android.os.Bundle metadata);
     method public java.util.List<androidx.credentials.CredentialOption> getCredentialOptions();
     method public String? getOrigin();
     method public boolean getPreferIdentityDocUi();
     method public android.content.ComponentName? getPreferUiBrandingComponentName();
+    method @Discouraged(message="It should only be used by OEM services and library groups") public static android.os.Bundle getRequestMetadataBundle(androidx.credentials.GetCredentialRequest request);
     method public boolean preferImmediatelyAvailableCredentials();
     property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
     property public final String? origin;
     property public final boolean preferIdentityDocUi;
     property public final boolean preferImmediatelyAvailableCredentials;
     property public final android.content.ComponentName? preferUiBrandingComponentName;
+    field public static final androidx.credentials.GetCredentialRequest.Companion Companion;
   }
 
   public static final class GetCredentialRequest.Builder {
@@ -240,6 +254,12 @@
     method public androidx.credentials.GetCredentialRequest.Builder setPreferUiBrandingComponentName(android.content.ComponentName? component);
   }
 
+  public static final class GetCredentialRequest.Companion {
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") @RequiresApi(34) public androidx.credentials.GetCredentialRequest createFrom(android.credentials.GetCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") public androidx.credentials.GetCredentialRequest createFrom(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, String? origin, android.os.Bundle metadata);
+    method @Discouraged(message="It should only be used by OEM services and library groups") public android.os.Bundle getRequestMetadataBundle(androidx.credentials.GetCredentialRequest request);
+  }
+
   public final class GetCredentialResponse {
     ctor public GetCredentialResponse(androidx.credentials.Credential credential);
     method public androidx.credentials.Credential getCredential();
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index 0692467..aba3b90 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -11,8 +11,9 @@
   }
 
   public abstract class CreateCredentialRequest {
-    method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
-    method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(34) public static final androidx.credentials.CreateCredentialRequest createFrom(android.credentials.CreateCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getCredentialData();
     method public final androidx.credentials.CreateCredentialRequest.DisplayInfo getDisplayInfo();
@@ -33,15 +34,16 @@
   }
 
   public static final class CreateCredentialRequest.Companion {
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(34) public androidx.credentials.CreateCredentialRequest createFrom(android.credentials.CreateCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    method @Discouraged(message="It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
   }
 
   public static final class CreateCredentialRequest.DisplayInfo {
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId);
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, optional CharSequence? userDisplayName);
     ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, CharSequence? userDisplayName, String? preferDefaultProvider);
-    method @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
+    method @Discouraged(message="It is recommended to construct a DisplayInfo by directly using the DisplayInfo constructor") @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
     method public CharSequence? getUserDisplayName();
     method public CharSequence getUserId();
     property public final CharSequence? userDisplayName;
@@ -50,7 +52,7 @@
   }
 
   public static final class CreateCredentialRequest.DisplayInfo.Companion {
-    method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
+    method @Discouraged(message="It is recommended to construct a DisplayInfo by directly using the DisplayInfo constructor") @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo createFrom(android.os.Bundle from);
   }
 
   public abstract class CreateCredentialResponse {
@@ -133,6 +135,8 @@
   }
 
   public abstract class Credential {
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") @RequiresApi(34) public static final androidx.credentials.Credential createFrom(android.credentials.Credential credential);
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") public static final androidx.credentials.Credential createFrom(String type, android.os.Bundle data);
     method public final android.os.Bundle getData();
     method public final String getType();
     property public final android.os.Bundle data;
@@ -141,6 +145,8 @@
   }
 
   public static final class Credential.Companion {
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") @RequiresApi(34) public androidx.credentials.Credential createFrom(android.credentials.Credential credential);
+    method @Discouraged(message="It is recommended to construct a Credential by directly instantiating a Credential subclass") public androidx.credentials.Credential createFrom(String type, android.os.Bundle data);
   }
 
   public interface CredentialManager {
@@ -174,6 +180,8 @@
   }
 
   public abstract class CredentialOption {
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") @RequiresApi(34) public static final androidx.credentials.CredentialOption createFrom(android.credentials.CredentialOption option);
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") public static final androidx.credentials.CredentialOption createFrom(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, java.util.Set<android.content.ComponentName> allowedProviders);
     method public final java.util.Set<android.content.ComponentName> getAllowedProviders();
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getRequestData();
@@ -196,6 +204,8 @@
   }
 
   public static final class CredentialOption.Companion {
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") @RequiresApi(34) public androidx.credentials.CredentialOption createFrom(android.credentials.CredentialOption option);
+    method @Discouraged(message="It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass") public androidx.credentials.CredentialOption createFrom(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, java.util.Set<android.content.ComponentName> allowedProviders);
   }
 
   public interface CredentialProvider {
@@ -217,16 +227,20 @@
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi, optional android.content.ComponentName? preferUiBrandingComponentName);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin, optional boolean preferIdentityDocUi, optional android.content.ComponentName? preferUiBrandingComponentName, optional boolean preferImmediatelyAvailableCredentials);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") @RequiresApi(34) public static androidx.credentials.GetCredentialRequest createFrom(android.credentials.GetCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") public static androidx.credentials.GetCredentialRequest createFrom(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, String? origin, android.os.Bundle metadata);
     method public java.util.List<androidx.credentials.CredentialOption> getCredentialOptions();
     method public String? getOrigin();
     method public boolean getPreferIdentityDocUi();
     method public android.content.ComponentName? getPreferUiBrandingComponentName();
+    method @Discouraged(message="It should only be used by OEM services and library groups") public static android.os.Bundle getRequestMetadataBundle(androidx.credentials.GetCredentialRequest request);
     method public boolean preferImmediatelyAvailableCredentials();
     property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
     property public final String? origin;
     property public final boolean preferIdentityDocUi;
     property public final boolean preferImmediatelyAvailableCredentials;
     property public final android.content.ComponentName? preferUiBrandingComponentName;
+    field public static final androidx.credentials.GetCredentialRequest.Companion Companion;
   }
 
   public static final class GetCredentialRequest.Builder {
@@ -240,6 +254,12 @@
     method public androidx.credentials.GetCredentialRequest.Builder setPreferUiBrandingComponentName(android.content.ComponentName? component);
   }
 
+  public static final class GetCredentialRequest.Companion {
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") @RequiresApi(34) public androidx.credentials.GetCredentialRequest createFrom(android.credentials.GetCredentialRequest request);
+    method @Discouraged(message="It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest") public androidx.credentials.GetCredentialRequest createFrom(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, String? origin, android.os.Bundle metadata);
+    method @Discouraged(message="It should only be used by OEM services and library groups") public android.os.Bundle getRequestMetadataBundle(androidx.credentials.GetCredentialRequest request);
+  }
+
   public final class GetCredentialResponse {
     ctor public GetCredentialResponse(androidx.credentials.Credential credential);
     method public androidx.credentials.Credential getCredential();
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
index 5ecdf5e..3d75ca8 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
@@ -90,7 +90,7 @@
         assertThat(request.origin).isEqualTo(expectedOrigin)
     }
 
-    @SdkSuppress(minSdkVersion = 23)
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
         val expectedType = "TYPE"
@@ -145,4 +145,63 @@
         assertThat(actualRequest.preferImmediatelyAvailableCredentials)
             .isEqualTo(expectedPreferImmediatelyAvailableCredentials)
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val expectedType = "TYPE"
+        val expectedCredentialDataBundle = Bundle()
+        expectedCredentialDataBundle.putString("Test", "Test")
+        val expectedCandidateQueryDataBundle = Bundle()
+        expectedCandidateQueryDataBundle.putBoolean("key", true)
+        val expectedDisplayInfo = DisplayInfo("userId")
+        val expectedSystemProvider = true
+        val expectedAutoSelectAllowed = true
+        val expectedPreferImmediatelyAvailableCredentials = true
+        val expectedOrigin = "Origin"
+        val request =
+            CreateCustomCredentialRequest(
+                expectedType,
+                expectedCredentialDataBundle,
+                expectedCandidateQueryDataBundle,
+                expectedSystemProvider,
+                expectedDisplayInfo,
+                expectedAutoSelectAllowed,
+                expectedOrigin,
+                expectedPreferImmediatelyAvailableCredentials,
+            )
+        val finalCredentialData = request.credentialData
+        finalCredentialData.putBundle(
+            DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO,
+            expectedDisplayInfo.toBundle()
+        )
+
+        val convertedRequest =
+            createFrom(
+                android.credentials.CreateCredentialRequest.Builder(
+                        request.type,
+                        request.credentialData,
+                        request.candidateQueryData
+                    )
+                    .setOrigin(expectedOrigin)
+                    .setIsSystemProviderRequired(request.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertThat(convertedRequest).isInstanceOf(CreateCustomCredentialRequest::class.java)
+        val actualRequest = convertedRequest as CreateCustomCredentialRequest
+        assertThat(actualRequest.type).isEqualTo(expectedType)
+        assertThat(equals(actualRequest.credentialData, expectedCredentialDataBundle)).isTrue()
+        assertThat(equals(actualRequest.candidateQueryData, expectedCandidateQueryDataBundle))
+            .isTrue()
+        assertThat(actualRequest.isSystemProviderRequired).isEqualTo(expectedSystemProvider)
+        assertThat(actualRequest.isAutoSelectAllowed).isEqualTo(expectedAutoSelectAllowed)
+        assertThat(actualRequest.displayInfo.userId).isEqualTo(expectedDisplayInfo.userId)
+        assertThat(actualRequest.displayInfo.userDisplayName)
+            .isEqualTo(expectedDisplayInfo.userDisplayName)
+        assertThat(actualRequest.origin).isEqualTo(expectedOrigin)
+        assertThat(actualRequest.origin).isEqualTo(expectedOrigin)
+        assertThat(actualRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(expectedPreferImmediatelyAvailableCredentials)
+    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
index 2654035..2491f72 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
@@ -189,7 +189,7 @@
             .isEqualTo(R.drawable.ic_password)
     }
 
-    @SdkSuppress(minSdkVersion = 34)
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
         val idExpected = "id"
@@ -248,4 +248,68 @@
         assertThat(convertedRequest.candidateQueryData.getBoolean(customCandidateQueryDataKey))
             .isEqualTo(customCandidateQueryDataValue)
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val idExpected = "id"
+        val passwordExpected = "pwd"
+        val preferImmediatelyAvailableCredentialsExpected = true
+        val isAutoSelectAllowedExpected = true
+        val originExpected = "origin"
+        val defaultProviderExpected = "com.test/com.test.TestProviderComponent"
+        val request =
+            CreatePasswordRequest(
+                idExpected,
+                passwordExpected,
+                originExpected,
+                defaultProviderExpected,
+                preferImmediatelyAvailableCredentialsExpected,
+                isAutoSelectAllowedExpected
+            )
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val credentialData = getFinalCreateCredentialData(request, mContext)
+        val customRequestDataKey = "customRequestDataKey"
+        val customRequestDataValue = "customRequestDataValue"
+        credentialData.putString(customRequestDataKey, customRequestDataValue)
+        request.credentialData.putAll(credentialData)
+        val candidateQueryData = request.candidateQueryData
+        val customCandidateQueryDataKey = "customRequestDataKey"
+        val customCandidateQueryDataValue = true
+        candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue)
+
+        val convertedRequest =
+            createFrom(
+                android.credentials.CreateCredentialRequest.Builder(
+                        request.type,
+                        credentialData,
+                        candidateQueryData
+                    )
+                    .setOrigin(originExpected)
+                    .setIsSystemProviderRequired(request.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertThat(convertedRequest).isInstanceOf(CreatePasswordRequest::class.java)
+        val convertedCreatePasswordRequest = convertedRequest as CreatePasswordRequest
+        assertThat(convertedCreatePasswordRequest.password).isEqualTo(passwordExpected)
+        assertThat(convertedCreatePasswordRequest.id).isEqualTo(idExpected)
+        assertThat(convertedCreatePasswordRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(preferImmediatelyAvailableCredentialsExpected)
+        assertThat(convertedCreatePasswordRequest.origin).isEqualTo(originExpected)
+        assertThat(convertedCreatePasswordRequest.isAutoSelectAllowed)
+            .isEqualTo(isAutoSelectAllowedExpected)
+        val displayInfo = convertedCreatePasswordRequest.displayInfo
+        assertThat(displayInfo.userDisplayName).isNull()
+        assertThat(displayInfo.userId).isEqualTo(idExpected)
+        assertThat(displayInfo.credentialTypeIcon!!.resId).isEqualTo(R.drawable.ic_password)
+        assertThat(displayInfo.preferDefaultProvider).isEqualTo(defaultProviderExpected)
+        assertThat(convertedRequest.credentialData.getString(customRequestDataKey))
+            .isEqualTo(customRequestDataValue)
+        assertThat(convertedRequest.candidateQueryData.getBoolean(customCandidateQueryDataKey))
+            .isEqualTo(customCandidateQueryDataValue)
+    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
index b6bb51f..71072855 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
@@ -211,7 +211,7 @@
             .isEqualTo(R.drawable.ic_passkey)
     }
 
-    @SdkSuppress(minSdkVersion = 34)
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
         val clientDataHashExpected = "hash".toByteArray()
@@ -266,4 +266,63 @@
         assertThat(convertedRequest.candidateQueryData.getBoolean(customCandidateQueryDataKey))
             .isEqualTo(customCandidateQueryDataValue)
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val clientDataHashExpected = "hash".toByteArray()
+        val originExpected = "origin"
+        val preferImmediatelyAvailableCredentialsExpected = true
+        val isAutoSelectAllowedExpected = true
+        val request =
+            CreatePublicKeyCredentialRequest(
+                TEST_REQUEST_JSON,
+                clientDataHashExpected,
+                preferImmediatelyAvailableCredentialsExpected,
+                originExpected,
+                isAutoSelectAllowedExpected
+            )
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val credentialData = getFinalCreateCredentialData(request, mContext)
+        val customRequestDataKey = "customRequestDataKey"
+        val customRequestDataValue = "customRequestDataValue"
+        credentialData.putString(customRequestDataKey, customRequestDataValue)
+        val candidateQueryData = request.candidateQueryData
+        val customCandidateQueryDataKey = "customRequestDataKey"
+        val customCandidateQueryDataValue = true
+        candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue)
+
+        val convertedRequest =
+            createFrom(
+                android.credentials.CreateCredentialRequest.Builder(
+                        request.type,
+                        credentialData,
+                        candidateQueryData
+                    )
+                    .setOrigin(originExpected)
+                    .setIsSystemProviderRequired(request.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertThat(convertedRequest).isInstanceOf(CreatePublicKeyCredentialRequest::class.java)
+        val convertedSubclassRequest = convertedRequest as CreatePublicKeyCredentialRequest
+        assertThat(convertedSubclassRequest.requestJson).isEqualTo(request.requestJson)
+        assertThat(convertedSubclassRequest.origin).isEqualTo(originExpected)
+        assertThat(convertedSubclassRequest.clientDataHash).isEqualTo(clientDataHashExpected)
+        assertThat(convertedSubclassRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(preferImmediatelyAvailableCredentialsExpected)
+        assertThat(convertedSubclassRequest.isAutoSelectAllowed)
+            .isEqualTo(isAutoSelectAllowedExpected)
+        val displayInfo = convertedSubclassRequest.displayInfo
+        assertThat(displayInfo.userDisplayName).isEqualTo(TEST_USER_DISPLAYNAME)
+        assertThat(displayInfo.userId).isEqualTo(TEST_USERNAME)
+        assertThat(displayInfo.credentialTypeIcon!!.resId).isEqualTo(R.drawable.ic_passkey)
+        assertThat(convertedRequest.credentialData.getString(customRequestDataKey))
+            .isEqualTo(customRequestDataValue)
+        assertThat(convertedRequest.candidateQueryData.getBoolean(customCandidateQueryDataKey))
+            .isEqualTo(customCandidateQueryDataValue)
+    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CustomCredentialTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CustomCredentialTest.kt
index edf0d9a..87e38e1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CustomCredentialTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CustomCredentialTest.kt
@@ -17,7 +17,9 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.credentials.Credential.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -51,4 +53,18 @@
         assertThat(option.type).isEqualTo(expectedType)
         assertThat(equals(option.data, expectedBundle)).isTrue()
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val expectedType = "TYPE"
+        val expectedBundle = Bundle()
+        expectedBundle.putString("Test", "Test")
+        val credential = CustomCredential(expectedType, expectedBundle)
+
+        val convertedCredential =
+            createFrom(android.credentials.Credential(credential.type, credential.data))
+
+        equals(convertedCredential, credential)
+    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java
index 4232a1b..73c8083 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java
@@ -220,7 +220,7 @@
 
 
         GetCredentialRequest convertedRequest = GetCredentialRequest.createFrom(
-                options, request.getOrigin(), GetCredentialRequest.toRequestDataBundle(request)
+                options, request.getOrigin(), GetCredentialRequest.getRequestMetadataBundle(request)
         );
 
         assertThat(convertedRequest.getOrigin()).isEqualTo(expectedOrigin);
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt
index bf7c7c5..65961ef 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt
@@ -18,8 +18,9 @@
 
 import android.content.ComponentName
 import androidx.credentials.GetCredentialRequest.Companion.createFrom
-import androidx.credentials.GetCredentialRequest.Companion.toRequestDataBundle
+import androidx.credentials.GetCredentialRequest.Companion.getRequestMetadataBundle
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
@@ -231,7 +232,52 @@
                 expectedPreferImmediatelyAvailableCredentials
             )
 
-        val convertedRequest = createFrom(options, request.origin, toRequestDataBundle(request))
+        val convertedRequest =
+            createFrom(options, request.origin, getRequestMetadataBundle(request))
+
+        assertThat(convertedRequest.origin).isEqualTo(expectedOrigin)
+        assertThat(convertedRequest.preferIdentityDocUi).isEqualTo(expectedPreferIdentityDocUi)
+        assertThat(convertedRequest.preferUiBrandingComponentName).isEqualTo(expectedComponentName)
+        assertThat(convertedRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(expectedPreferImmediatelyAvailableCredentials)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val options = java.util.ArrayList<CredentialOption>()
+        options.add(GetPasswordOption())
+        val expectedPreferImmediatelyAvailableCredentials = true
+        val expectedComponentName = ComponentName("test pkg", "test cls")
+        val expectedPreferIdentityDocUi = true
+        val expectedOrigin = "origin"
+        val request =
+            GetCredentialRequest(
+                options,
+                expectedOrigin,
+                expectedPreferIdentityDocUi,
+                expectedComponentName,
+                expectedPreferImmediatelyAvailableCredentials
+            )
+
+        val convertedRequest =
+            createFrom(
+                android.credentials.GetCredentialRequest.Builder(getRequestMetadataBundle(request))
+                    .setOrigin(expectedOrigin)
+                    .setCredentialOptions(
+                        options.map {
+                            android.credentials.CredentialOption.Builder(
+                                    it.type,
+                                    it.requestData,
+                                    it.candidateQueryData
+                                )
+                                .setAllowedProviders(it.allowedProviders)
+                                .setIsSystemProviderRequired(it.isSystemProviderRequired)
+                                .build()
+                        }
+                    )
+                    .build()
+            )
 
         assertThat(convertedRequest.origin).isEqualTo(expectedOrigin)
         assertThat(convertedRequest.preferIdentityDocUi).isEqualTo(expectedPreferIdentityDocUi)
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
index ab3291b..c9c5e8a 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
@@ -20,6 +20,7 @@
 import android.os.Bundle
 import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -153,6 +154,45 @@
         assertThat(actualOption.typePriorityHint).isEqualTo(expectedPriorityHint)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val expectedType = "TYPE"
+        val expectedBundle = Bundle()
+        expectedBundle.putString("Test", "Test")
+        val expectedCandidateQueryDataBundle = Bundle()
+        expectedCandidateQueryDataBundle.putBoolean("key", true)
+        val expectedSystemProvider = true
+        val expectedAutoSelectAllowed = false
+        val expectedAllowedProviders: Set<ComponentName> =
+            setOf(ComponentName("pkg", "cls"), ComponentName("pkg2", "cls2"))
+        val expectedPriorityHint = CredentialOption.PRIORITY_OIDC_OR_SIMILAR
+        val option =
+            GetCustomCredentialOption(
+                expectedType,
+                expectedBundle,
+                expectedCandidateQueryDataBundle,
+                expectedSystemProvider,
+                expectedAutoSelectAllowed,
+                expectedAllowedProviders,
+                expectedPriorityHint
+            )
+
+        val convertedOption =
+            createFrom(
+                android.credentials.CredentialOption.Builder(
+                        option.type,
+                        option.requestData,
+                        option.candidateQueryData
+                    )
+                    .setAllowedProviders(option.allowedProviders)
+                    .setIsSystemProviderRequired(option.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertEquals(convertedOption, option)
+    }
+
     private companion object {
         private const val EXPECTED_CUSTOM_DEFAULT_PRIORITY = CredentialOption.PRIORITY_DEFAULT
     }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
index b32e9ef..a6a47e7 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
@@ -20,6 +20,7 @@
 import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.credentials.GetPasswordOption.Companion.BUNDLE_KEY_ALLOWED_USER_IDS
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.collect.ImmutableSet
 import com.google.common.truth.Truth.assertThat
@@ -154,6 +155,47 @@
         assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSWORD_PRIORITY)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val expectedIsAutoSelectAllowed = true
+        val expectedAllowedProviders: Set<ComponentName> =
+            ImmutableSet.of(ComponentName("pkg", "cls"), ComponentName("pkg2", "cls2"))
+        val expectedAllowedUserIds: Set<String> = ImmutableSet.of("id1", "id2", "id3")
+        val option =
+            GetPasswordOption(
+                expectedAllowedUserIds,
+                expectedIsAutoSelectAllowed,
+                expectedAllowedProviders
+            )
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val requestData = option.requestData
+        val customRequestDataKey = "customRequestDataKey"
+        val customRequestDataValue = "customRequestDataValue"
+        requestData.putString(customRequestDataKey, customRequestDataValue)
+        val candidateQueryData = option.candidateQueryData
+        val customCandidateQueryDataKey = "customRequestDataKey"
+        val customCandidateQueryDataValue = true
+        candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue)
+
+        val convertedOption =
+            createFrom(
+                android.credentials.CredentialOption.Builder(
+                        option.type,
+                        requestData,
+                        candidateQueryData
+                    )
+                    .setAllowedProviders(option.allowedProviders)
+                    .setIsSystemProviderRequired(option.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertEquals(convertedOption, option)
+    }
+
     private companion object {
         const val EXPECTED_PASSWORD_PRIORITY = CredentialOption.PRIORITY_PASSWORD_OR_SIMILAR
     }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
index 5cb1d34..b91e0bb 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
@@ -21,6 +21,7 @@
 import androidx.credentials.CredentialOption.Companion.BUNDLE_KEY_TYPE_PRIORITY_VALUE
 import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.collect.ImmutableSet
 import com.google.common.truth.Truth.assertThat
@@ -150,6 +151,46 @@
             .isEqualTo(customCandidateQueryDataValue)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val clientDataHash = "hash".toByteArray()
+        val expectedAllowedProviders: Set<ComponentName> =
+            ImmutableSet.of(ComponentName("pkg", "cls"), ComponentName("pkg2", "cls2"))
+        val option =
+            GetPublicKeyCredentialOption(
+                TEST_REQUEST_JSON,
+                clientDataHash,
+                expectedAllowedProviders
+            )
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val requestData = option.requestData
+        val customRequestDataKey = "customRequestDataKey"
+        val customRequestDataValue = "customRequestDataValue"
+        requestData.putString(customRequestDataKey, customRequestDataValue)
+        val candidateQueryData = option.candidateQueryData
+        val customCandidateQueryDataKey = "customRequestDataKey"
+        val customCandidateQueryDataValue = true
+        candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue)
+
+        val convertedOption =
+            createFrom(
+                android.credentials.CredentialOption.Builder(
+                        option.type,
+                        requestData,
+                        candidateQueryData
+                    )
+                    .setAllowedProviders(option.allowedProviders)
+                    .setIsSystemProviderRequired(option.isSystemProviderRequired)
+                    .build()
+            )
+
+        assertEquals(convertedOption, option)
+    }
+
     companion object Constant {
         private const val TEST_REQUEST_JSON = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         const val EXPECTED_PASSKEY_PRIORITY = CredentialOption.PRIORITY_PASSKEY_OR_SIMILAR
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialTest.kt
index 81cfcb9b..656f68c 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialTest.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.credentials.Credential.Companion.createFrom
 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
@@ -89,4 +90,22 @@
         assertThat(convertedCredential.data.getCharSequence(customDataKey))
             .isEqualTo(customDataValue)
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val credential = PasswordCredential("id", "password")
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val data = credential.data
+        val customDataKey = "customRequestDataKey"
+        val customDataValue: CharSequence = "customRequestDataValue"
+        data.putCharSequence(customDataKey, customDataValue)
+
+        val convertedCredential = createFrom(android.credentials.Credential(credential.type, data))
+
+        equals(convertedCredential, credential)
+    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/PublicKeyCredentialTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/PublicKeyCredentialTest.kt
index 47c095f..b44ec90 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/PublicKeyCredentialTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/PublicKeyCredentialTest.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.credentials.Credential.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -90,7 +91,7 @@
     }
 
     @Test
-    fun frameworkConversion_success() {
+    fun frameworkConversion_allApis_success() {
         val credential = PublicKeyCredential(TEST_JSON)
         // Add additional data to the request data and candidate query data to make sure
         // they persist after the conversion
@@ -111,6 +112,24 @@
             .isEqualTo(customDataValue)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkConversion_frameworkClass_success() {
+        val credential = PublicKeyCredential(TEST_JSON)
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        // Add additional data to the request data and candidate query data to make sure
+        // they persist after the conversion
+        val data = credential.data
+        val customDataKey = "customRequestDataKey"
+        val customDataValue: CharSequence = "customRequestDataValue"
+        data.putCharSequence(customDataKey, customDataValue)
+
+        val convertedCredential = createFrom(android.credentials.Credential(credential.type, data))
+
+        equals(convertedCredential, credential)
+    }
+
     @Test
     fun staticProperty_hasCorrectTypeConstantValue() {
         val typeExpected = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
index 1e5593c..4cb0d808 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
@@ -27,6 +27,7 @@
 
 import androidx.biometric.BiometricManager;
 import androidx.biometric.BiometricPrompt;
+import androidx.credentials.provider.utils.BiometricTestUtils;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -34,15 +35,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import javax.crypto.NullCipher;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SdkSuppress(minSdkVersion = 35)
 public class BiometricPromptDataJavaTest {
 
-    private static final BiometricPrompt.CryptoObject TEST_CRYPTO_OBJECT = new
-            BiometricPrompt.CryptoObject(new NullCipher());
+    private static final BiometricPrompt.CryptoObject TEST_CRYPTO_OBJECT = BiometricTestUtils
+            .INSTANCE.createCryptoObject$credentials_debugAndroidTest();
 
     private static final long DEFAULT_BUNDLE_LONG_FOR_CRYPTO_ID = 0L;
 
@@ -136,7 +135,6 @@
                 .isEqualTo(TEST_ALLOWED_AUTHENTICATOR);
     }
 
-    @SdkSuppress(maxSdkVersion = 34)
     @Test
     public void fromBundle_validAllowedAuthenticator_success() {
         Bundle inputBundle = new Bundle();
@@ -150,13 +148,12 @@
         assertThat(actualBiometricPromptData.getCryptoObject()).isNull();
     }
 
-    @SdkSuppress(minSdkVersion = 35)
     @Test
     public void fromBundle_validAllowedAuthenticatorAboveApi35_success() {
-        int expectedOpId = Integer.MIN_VALUE;
+        long expectedOpId = TEST_CRYPTO_OBJECT.getOperationHandle();
         Bundle inputBundle = new Bundle();
         inputBundle.putInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS, TEST_ALLOWED_AUTHENTICATOR);
-        inputBundle.putInt(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId);
+        inputBundle.putLong(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId);
 
         BiometricPromptData actualBiometricPromptData = BiometricPromptData.fromBundle(inputBundle);
 
@@ -164,24 +161,21 @@
         assertThat(actualBiometricPromptData.getAllowedAuthenticators()).isEqualTo(
                 TEST_ALLOWED_AUTHENTICATOR);
         assertThat(actualBiometricPromptData.getCryptoObject()).isNotNull();
-        assertThat(actualBiometricPromptData.getCryptoObject().hashCode())
-                .isEqualTo(expectedOpId);
+        assertThat(actualBiometricPromptData.getCryptoObject().getOperationHandle())
+                .isEqualTo(TEST_CRYPTO_OBJECT.getOperationHandle());
     }
 
     @Test
     public void fromBundle_unrecognizedAllowedAuthenticator_success() {
-        int expectedOpId = Integer.MIN_VALUE;
         Bundle inputBundle = new Bundle();
         int unrecognizedAuthenticator = Integer.MAX_VALUE;
         inputBundle.putInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS, unrecognizedAuthenticator);
-        inputBundle.putInt(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId);
 
         BiometricPromptData actualBiometricPromptData = BiometricPromptData.fromBundle(inputBundle);
 
         assertThat(actualBiometricPromptData).isNotNull();
         assertThat(actualBiometricPromptData.getAllowedAuthenticators())
                 .isEqualTo(unrecognizedAuthenticator);
-
     }
 
     @Test
@@ -197,7 +191,6 @@
         assertThat(actualBiometricPromptData).isNull();
     }
 
-    @SdkSuppress(maxSdkVersion = 34)
     @Test
     public void toBundle_success() {
         BiometricPromptData testBiometricPromptData = new BiometricPromptData(/*cryptoObject=*/null,
@@ -214,12 +207,11 @@
                 DEFAULT_BUNDLE_LONG_FOR_CRYPTO_ID);
     }
 
-    @SdkSuppress(minSdkVersion = 35)
     @Test
     public void toBundle_api35AndAboveWithOpId_success() {
         BiometricPromptData testBiometricPromptData = new BiometricPromptData(TEST_CRYPTO_OBJECT,
                 TEST_ALLOWED_AUTHENTICATOR);
-        long expectedOpId = TEST_CRYPTO_OBJECT.hashCode();
+        long expectedOpId = TEST_CRYPTO_OBJECT.getOperationHandle();
 
         Bundle actualBundle = BiometricPromptData.toBundle(
                 testBiometricPromptData);
@@ -228,7 +220,7 @@
         assertThat(actualBundle.getInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS)).isEqualTo(
                 TEST_ALLOWED_AUTHENTICATOR
         );
-        assertThat(actualBundle.getInt(BUNDLE_HINT_CRYPTO_OP_ID)).isEqualTo(expectedOpId);
+        assertThat(actualBundle.getLong(BUNDLE_HINT_CRYPTO_OP_ID)).isEqualTo(expectedOpId);
     }
 
     @Test
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
index 399a783..bdc1ea0 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
@@ -18,14 +18,13 @@
 
 import android.os.Bundle
 import androidx.biometric.BiometricManager
-import androidx.biometric.BiometricPrompt
 import androidx.credentials.provider.BiometricPromptData.Companion.BUNDLE_HINT_ALLOWED_AUTHENTICATORS
 import androidx.credentials.provider.BiometricPromptData.Companion.BUNDLE_HINT_CRYPTO_OP_ID
+import androidx.credentials.provider.utils.BiometricTestUtils
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
-import javax.crypto.NullCipher
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -129,7 +128,6 @@
         }
     }
 
-    @SdkSuppress(maxSdkVersion = 34)
     @Test
     fun fromBundle_validAllowedAuthenticator_success() {
         val inputBundle = Bundle()
@@ -143,13 +141,12 @@
         assertThat(actualBiometricPromptData.cryptoObject).isNull()
     }
 
-    @SdkSuppress(minSdkVersion = 35)
     @Test
     fun fromBundle_validAllowedAuthenticatorAboveApi35_success() {
-        val expectedOpId = Integer.MIN_VALUE
+        val expectedOpId = TEST_CRYPTO_OBJECT.operationHandle
         val inputBundle = Bundle()
         inputBundle.putInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS, TEST_ALLOWED_AUTHENTICATOR)
-        inputBundle.putInt(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId)
+        inputBundle.putLong(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId)
 
         val actualBiometricPromptData = BiometricPromptData.fromBundle(inputBundle)
 
@@ -157,7 +154,8 @@
         assertThat(actualBiometricPromptData!!.allowedAuthenticators)
             .isEqualTo(TEST_ALLOWED_AUTHENTICATOR)
         assertThat(actualBiometricPromptData.cryptoObject).isNotNull()
-        assertThat(actualBiometricPromptData.cryptoObject!!.hashCode()).isEqualTo(expectedOpId)
+        assertThat(actualBiometricPromptData.cryptoObject!!.operationHandle)
+            .isEqualTo(TEST_CRYPTO_OBJECT.operationHandle)
     }
 
     @Test
@@ -186,7 +184,6 @@
         assertThat(actualBiometricPromptData).isNull()
     }
 
-    @SdkSuppress(maxSdkVersion = 34)
     @Test
     fun toBundle_success() {
         val testBiometricPromptData =
@@ -201,23 +198,22 @@
             .isEqualTo(DEFAULT_BUNDLE_LONG_FOR_CRYPTO_ID)
     }
 
-    @SdkSuppress(minSdkVersion = 35)
     @Test
     fun toBundle_api35AndAboveWithOpId_success() {
         val testBiometricPromptData =
             BiometricPromptData(TEST_CRYPTO_OBJECT, TEST_ALLOWED_AUTHENTICATOR)
-        val expectedOpId = TEST_CRYPTO_OBJECT.hashCode()
+        val expectedOpId = TEST_CRYPTO_OBJECT.operationHandle
 
         val actualBundle = BiometricPromptData.toBundle(testBiometricPromptData)
 
         assertThat(actualBundle).isNotNull()
         assertThat(actualBundle.getInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS))
             .isEqualTo(TEST_ALLOWED_AUTHENTICATOR)
-        assertThat(actualBundle.getInt(BUNDLE_HINT_CRYPTO_OP_ID)).isEqualTo(expectedOpId)
+        assertThat(actualBundle.getLong(BUNDLE_HINT_CRYPTO_OP_ID)).isEqualTo(expectedOpId)
     }
 
     private companion object {
-        private val TEST_CRYPTO_OBJECT = BiometricPrompt.CryptoObject(NullCipher())
+        private val TEST_CRYPTO_OBJECT = BiometricTestUtils.createCryptoObject()
 
         private const val DEFAULT_BUNDLE_LONG_FOR_CRYPTO_ID = 0L
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
index 83a2d1d..6816e8d 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
@@ -16,6 +16,8 @@
 
 package androidx.credentials.provider.ui;
 
+import static androidx.credentials.provider.ui.UiUtils.testBiometricPromptData;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertNotNull;
@@ -29,11 +31,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Build;
 
-import androidx.annotation.RequiresApi;
-import androidx.biometric.BiometricManager;
-import androidx.biometric.BiometricPrompt;
 import androidx.core.os.BuildCompat;
-import androidx.credentials.provider.BiometricPromptData;
 import androidx.credentials.provider.CreateEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -45,8 +43,6 @@
 
 import java.time.Instant;
 
-import javax.crypto.NullCipher;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26) // Instant usage
 @SmallTest
@@ -204,12 +200,4 @@
                     testBiometricPromptData().getAllowedAuthenticators());
         }
     }
-
-    @RequiresApi(35)
-    private static BiometricPromptData testBiometricPromptData() {
-        return new BiometricPromptData.Builder()
-                .setCryptoObject(new BiometricPrompt.CryptoObject(new NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build();
-    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
index 4c6810e..2f8838b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
@@ -21,22 +21,18 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.biometric.BiometricManager
-import androidx.biometric.BiometricPrompt
 import androidx.core.os.BuildCompat
-import androidx.credentials.provider.BiometricPromptData
 import androidx.credentials.provider.CreateEntry
 import androidx.credentials.provider.CreateEntry.Companion.fromCreateEntry
 import androidx.credentials.provider.CreateEntry.Companion.fromSlice
 import androidx.credentials.provider.CreateEntry.Companion.toSlice
+import androidx.credentials.provider.ui.UiUtils.Companion.testBiometricPromptData
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
-import javax.crypto.NullCipher
 import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -208,13 +204,5 @@
         private const val LAST_USED_TIME = 10L
         private val ICON =
             Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
-
-        @RequiresApi(35)
-        private fun testBiometricPromptData(): BiometricPromptData {
-            return BiometricPromptData.Builder()
-                .setCryptoObject(BiometricPrompt.CryptoObject(NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build()
-        }
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
index d93cd88..00f5651 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
@@ -16,6 +16,7 @@
 package androidx.credentials.provider.ui;
 
 import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+import static androidx.credentials.provider.ui.UiUtils.testBiometricPromptData;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -32,15 +33,11 @@
 import android.os.Bundle;
 import android.service.credentials.CredentialEntry;
 
-import androidx.annotation.RequiresApi;
-import androidx.biometric.BiometricManager;
-import androidx.biometric.BiometricPrompt;
 import androidx.core.os.BuildCompat;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
 import androidx.credentials.provider.BeginGetCredentialOption;
 import androidx.credentials.provider.BeginGetCustomCredentialOption;
-import androidx.credentials.provider.BiometricPromptData;
 import androidx.credentials.provider.CustomCredentialEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,8 +49,6 @@
 
 import java.time.Instant;
 
-import javax.crypto.NullCipher;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26) // Instant usage
 @SmallTest
@@ -387,12 +382,4 @@
             assertThat(entry.getBiometricPromptData()).isNull();
         }
     }
-
-    @RequiresApi(35)
-    private static BiometricPromptData testBiometricPromptData() {
-        return new BiometricPromptData.Builder()
-            .setCryptoObject(new BiometricPrompt.CryptoObject(new NullCipher()))
-            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-            .build();
-    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
index 70aa96d..235ccf4 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
@@ -22,27 +22,23 @@
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.service.credentials.CredentialEntry
-import androidx.annotation.RequiresApi
-import androidx.biometric.BiometricManager
-import androidx.biometric.BiometricPrompt
 import androidx.core.os.BuildCompat
 import androidx.credentials.CredentialOption
 import androidx.credentials.R
 import androidx.credentials.equals
 import androidx.credentials.provider.BeginGetCredentialOption
 import androidx.credentials.provider.BeginGetCustomCredentialOption
-import androidx.credentials.provider.BiometricPromptData
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.CustomCredentialEntry.Companion.fromCredentialEntry
 import androidx.credentials.provider.CustomCredentialEntry.Companion.fromSlice
 import androidx.credentials.provider.CustomCredentialEntry.Companion.toSlice
+import androidx.credentials.provider.ui.UiUtils.Companion.testBiometricPromptData
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
-import javax.crypto.NullCipher
 import org.junit.Assert
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertThrows
@@ -431,13 +427,5 @@
         private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
         private const val SINGLE_PROVIDER_ICON_BIT = true
         private const val ENTRY_GROUP_ID = "entryGroupId"
-
-        @RequiresApi(35)
-        private fun testBiometricPromptData(): BiometricPromptData {
-            return BiometricPromptData.Builder()
-                .setCryptoObject(BiometricPrompt.CryptoObject(NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build()
-        }
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
index 48ab2a9..da1e8b2 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
@@ -16,6 +16,7 @@
 package androidx.credentials.provider.ui;
 
 import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+import static androidx.credentials.provider.ui.UiUtils.testBiometricPromptData;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -32,15 +33,11 @@
 import android.os.Bundle;
 import android.service.credentials.CredentialEntry;
 
-import androidx.annotation.RequiresApi;
-import androidx.biometric.BiometricManager;
-import androidx.biometric.BiometricPrompt;
 import androidx.core.os.BuildCompat;
 import androidx.credentials.PasswordCredential;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
 import androidx.credentials.provider.BeginGetPasswordOption;
-import androidx.credentials.provider.BiometricPromptData;
 import androidx.credentials.provider.PasswordCredentialEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -53,8 +50,6 @@
 import java.time.Instant;
 import java.util.HashSet;
 
-import javax.crypto.NullCipher;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26) // Instant usage
 @SmallTest
@@ -401,12 +396,4 @@
             assertThat(entry.getBiometricPromptData()).isNull();
         }
     }
-
-    @RequiresApi(35)
-    private static BiometricPromptData testBiometricPromptData() {
-        return new BiometricPromptData.Builder()
-                .setCryptoObject(new BiometricPrompt.CryptoObject(new NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build();
-    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
index bd72745..a924a21 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
@@ -22,25 +22,21 @@
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.service.credentials.CredentialEntry
-import androidx.annotation.RequiresApi
-import androidx.biometric.BiometricManager
-import androidx.biometric.BiometricPrompt
 import androidx.core.os.BuildCompat
 import androidx.credentials.CredentialOption
 import androidx.credentials.PasswordCredential
 import androidx.credentials.R
 import androidx.credentials.equals
 import androidx.credentials.provider.BeginGetPasswordOption
-import androidx.credentials.provider.BiometricPromptData
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry.Companion.fromSlice
+import androidx.credentials.provider.ui.UiUtils.Companion.testBiometricPromptData
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
-import javax.crypto.NullCipher
 import junit.framework.TestCase.assertFalse
 import junit.framework.TestCase.assertNotNull
 import org.junit.Assert
@@ -401,13 +397,5 @@
         private val AFFILIATED_DOMAIN = "affiliation-name"
         private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
         private const val SINGLE_PROVIDER_ICON_BIT = true
-
-        @RequiresApi(35)
-        private fun testBiometricPromptData(): BiometricPromptData {
-            return BiometricPromptData.Builder()
-                .setCryptoObject(BiometricPrompt.CryptoObject(NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build()
-        }
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
index fff3b7e..ba76140 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
@@ -16,6 +16,7 @@
 package androidx.credentials.provider.ui;
 
 import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+import static androidx.credentials.provider.ui.UiUtils.testBiometricPromptData;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,15 +32,11 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
-import androidx.annotation.RequiresApi;
-import androidx.biometric.BiometricManager;
-import androidx.biometric.BiometricPrompt;
 import androidx.core.os.BuildCompat;
 import androidx.credentials.PublicKeyCredential;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
 import androidx.credentials.provider.BeginGetPublicKeyCredentialOption;
-import androidx.credentials.provider.BiometricPromptData;
 import androidx.credentials.provider.PublicKeyCredentialEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -51,8 +48,6 @@
 
 import java.time.Instant;
 
-import javax.crypto.NullCipher;
-
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 26) // Instant usage
 @SmallTest
@@ -272,12 +267,4 @@
             assertThat(entry.getBiometricPromptData()).isNull();
         }
     }
-
-    @RequiresApi(35)
-    private static BiometricPromptData testBiometricPromptData() {
-        return new BiometricPromptData.Builder()
-                .setCryptoObject(new BiometricPrompt.CryptoObject(new NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build();
-    }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
index d4e521e..c328433 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
@@ -22,27 +22,23 @@
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.service.credentials.CredentialEntry
-import androidx.annotation.RequiresApi
-import androidx.biometric.BiometricManager
-import androidx.biometric.BiometricPrompt
 import androidx.core.os.BuildCompat
 import androidx.credentials.CredentialOption
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.R
 import androidx.credentials.equals
 import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
-import androidx.credentials.provider.BiometricPromptData
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry.Companion.fromCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry.Companion.fromSlice
 import androidx.credentials.provider.PublicKeyCredentialEntry.Companion.toSlice
+import androidx.credentials.provider.ui.UiUtils.Companion.testBiometricPromptData
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
-import javax.crypto.NullCipher
 import junit.framework.TestCase.assertNotNull
 import org.junit.Assert
 import org.junit.Assert.assertThrows
@@ -327,13 +323,5 @@
         private const val IS_AUTO_SELECT_ALLOWED = true
         private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
         private const val SINGLE_PROVIDER_ICON_BIT = true
-
-        @RequiresApi(35)
-        private fun testBiometricPromptData(): BiometricPromptData {
-            return BiometricPromptData.Builder()
-                .setCryptoObject(BiometricPrompt.CryptoObject(NullCipher()))
-                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-                .build()
-        }
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/UiUtils.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/UiUtils.kt
index 96dd0b5..6df5116 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/UiUtils.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/UiUtils.kt
@@ -22,13 +22,17 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.biometric.BiometricManager
 import androidx.credentials.provider.Action
 import androidx.credentials.provider.AuthenticationAction
 import androidx.credentials.provider.BeginGetPasswordOption
+import androidx.credentials.provider.BiometricPromptData
 import androidx.credentials.provider.CreateEntry
 import androidx.credentials.provider.CredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.RemoteEntry
+import androidx.credentials.provider.utils.BiometricTestUtils
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
 
@@ -105,5 +109,13 @@
         fun constructRemoteEntry(): RemoteEntry {
             return RemoteEntry(sPendingIntent)
         }
+
+        @JvmStatic
+        @RequiresApi(35)
+        fun testBiometricPromptData(): BiometricPromptData =
+            BiometricPromptData.Builder()
+                .setCryptoObject(BiometricTestUtils.createCryptoObject())
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+                .build()
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt
new file mode 100644
index 0000000..b9ed941
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.provider.utils
+
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import androidx.annotation.RequiresApi
+import androidx.biometric.BiometricPrompt
+import java.security.KeyStore
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKey
+
+@RequiresApi(35)
+object BiometricTestUtils {
+
+    /** A name used to refer to an app-specified secret key. */
+    private const val KEY_NAME = "mySecretKey"
+
+    /** The name of the Android keystore provider instance. */
+    private const val KEYSTORE_INSTANCE = "AndroidKeyStore"
+
+    /**
+     * Returns a [BiometricPrompt.CryptoObject] for crypto-based authentication. Adapted from:
+     * [package androidx.biometric.samples.auth].
+     */
+    @RequiresApi(35)
+    internal fun createCryptoObject(): BiometricPrompt.CryptoObject {
+        val keyPurpose = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
+        val keySpec =
+            KeyGenParameterSpec.Builder(KEY_NAME, keyPurpose)
+                .apply {
+                    setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+                    setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+                    // Disable user authentication requirement for test purposes.
+                    // In reality, given this is primarily for biometric flows, Authenticator
+                    // Types are expected, but emulators used in testing lack a
+                    // lockscreen. This allows us to generate a more official
+                    // CryptoObject instead of relying on mocks with this compromise.
+                    setUserAuthenticationRequired(false)
+                }
+                .build()
+
+        KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_INSTANCE).run {
+            init(keySpec)
+            generateKey()
+        }
+
+        val cipher =
+            Cipher.getInstance(
+                    "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/" +
+                        KeyProperties.ENCRYPTION_PADDING_PKCS7
+                )
+                .apply {
+                    val keyStore = KeyStore.getInstance(KEYSTORE_INSTANCE).apply { load(null) }
+                    init(Cipher.ENCRYPT_MODE, keyStore.getKey(KEY_NAME, null) as SecretKey)
+                }
+
+        return BiometricPrompt.CryptoObject(cipher)
+    }
+}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
index 1d219bb..fa9b7ee 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
@@ -19,6 +19,7 @@
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.text.TextUtils
+import androidx.annotation.Discouraged
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.credentials.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
@@ -180,7 +181,10 @@
              *   [CreateCredentialRequest.credentialData]
              */
             @JvmStatic
-            @RequiresApi(23)
+            @RequiresApi(23) // Icon dependency
+            @Discouraged(
+                "It is recommended to construct a DisplayInfo by directly using the DisplayInfo constructor"
+            )
             fun createFrom(from: Bundle): DisplayInfo {
                 return try {
                     val displayInfoBundle = from.getBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO)!!
@@ -209,12 +213,39 @@
             "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
 
         /**
+         * Parses the [request] into an instance of [CreateCredentialRequest].
+         *
+         * @param request the framework CreateCredentialRequest object
+         */
+        @JvmStatic
+        @RequiresApi(34)
+        @Discouraged(
+            "It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass"
+        )
+        fun createFrom(
+            request: android.credentials.CreateCredentialRequest
+        ): CreateCredentialRequest {
+            return createFrom(
+                request.type,
+                request.credentialData,
+                request.candidateQueryData,
+                request.isSystemProviderRequired,
+                request.origin
+            )
+        }
+
+        /**
          * Attempts to parse the raw data into one of [CreatePasswordRequest],
          * [CreatePublicKeyCredentialRequest], and [CreateCustomCredentialRequest].
          *
          * @param type matches [CreateCredentialRequest.type]
-         * @param credentialData matches [CreateCredentialRequest.credentialData]
-         * @param candidateQueryData matches [CreateCredentialRequest.candidateQueryData]
+         * @param credentialData matches [CreateCredentialRequest.credentialData], the request data
+         *   in the [Bundle] format; this should be constructed and retrieved from the a given
+         *   [CreateCredentialRequest] itself and never be created from scratch
+         * @param candidateQueryData matches [CreateCredentialRequest.candidateQueryData], the
+         *   partial request data in the [Bundle] format that will be sent to the provider during
+         *   the initial candidate query stage; this should be constructed and retrieved from the a
+         *   given [CreateCredentialRequest] itself and never be created from scratch
          * @param requireSystemProvider whether the request must only be fulfilled by a system
          *   provider
          * @param origin the origin of a different application if the request is being made on
@@ -223,6 +254,9 @@
         @JvmStatic
         @JvmOverloads
         @RequiresApi(23)
+        @Discouraged(
+            "It is recommended to construct a CreateCredentialRequest by directly instantiating a CreateCredentialRequest subclass"
+        )
         fun createFrom(
             type: String,
             credentialData: Bundle,
diff --git a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
index 77bfdc7..ff80871 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
@@ -17,7 +17,8 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.annotation.RestrictTo
+import androidx.annotation.Discouraged
+import androidx.annotation.RequiresApi
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
@@ -35,8 +36,18 @@
     val data: Bundle,
 ) {
     companion object {
+        /**
+         * Parses the raw data into an instance of [Credential].
+         *
+         * @param type matches [Credential.type], the credential type
+         * @param data matches [Credential.data], the credential data in the [Bundle] format; this
+         *   should be constructed and retrieved from the a given [Credential] itself and never be
+         *   created from scratch
+         */
         @JvmStatic
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // used from java tests
+        @Discouraged(
+            "It is recommended to construct a Credential by directly instantiating a Credential subclass"
+        )
         fun createFrom(type: String, data: Bundle): Credential {
             return try {
                 when (type) {
@@ -53,5 +64,19 @@
                 CustomCredential(type, data)
             }
         }
+
+        /**
+         * Parses the [credential] into an instance of [Credential].
+         *
+         * @param credential the framework Credential object
+         */
+        @JvmStatic
+        @RequiresApi(34)
+        @Discouraged(
+            "It is recommended to construct a Credential by directly instantiating a Credential subclass"
+        )
+        fun createFrom(credential: android.credentials.Credential): Credential {
+            return createFrom(credential.type, credential.data)
+        }
     }
 }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
index 8d58380..92e17e4 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
@@ -18,7 +18,9 @@
 
 import android.content.ComponentName
 import android.os.Bundle
+import androidx.annotation.Discouraged
 import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.credentials.internal.FrameworkClassParsingException
 
@@ -53,8 +55,9 @@
  *   the only one available option
  * @property allowedProviders a set of provider service [ComponentName] allowed to receive this
  *   option (Note: a [SecurityException] will be thrown if it is set as non-empty but your app does
- *   not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; for API level < 34, this
- *   property will not take effect and you should control the allowed provider via
+ *   not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; empty means every
+ *   provider is eligible; for API level < 34, this property will not take effect and you should
+ *   control the allowed provider via
  *   [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies))
  * @property typePriorityHint sets the priority of this entry, which defines how it appears in the
  *   credential selector, with less precedence than account ordering but more precedence than last
@@ -113,8 +116,44 @@
             return data.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED)
         }
 
+        /**
+         * Parses the [option] into an instance of [CredentialOption].
+         *
+         * @param option the framework CredentialOption object
+         */
+        @RequiresApi(34)
         @JvmStatic
-        @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
+        @Discouraged(
+            "It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass"
+        )
+        fun createFrom(option: android.credentials.CredentialOption): CredentialOption {
+            return createFrom(
+                option.type,
+                option.credentialRetrievalData,
+                option.candidateQueryData,
+                option.isSystemProviderRequired,
+                option.allowedProviders
+            )
+        }
+
+        /**
+         * Parses the raw data into an instance of [CredentialOption].
+         *
+         * @param type matches [CredentialOption.type]
+         * @param requestData matches [CredentialOption.requestData], the request data in the
+         *   [Bundle] format; this should be constructed and retrieved from the a given
+         *   [CredentialOption] itself and never be created from scratch
+         * @param candidateQueryData matches [CredentialOption.candidateQueryData]; this should be
+         *   constructed and retrieved from the a given [CredentialOption] itself and never be
+         *   created from scratch
+         * @param requireSystemProvider matches [CredentialOption.isSystemProviderRequired]
+         * @param allowedProviders matches [CredentialOption.allowedProviders], empty means every
+         *   provider is eligible
+         */
+        @JvmStatic
+        @Discouraged(
+            "It is recommended to construct a CredentialOption by directly instantiating a CredentialOption subclass"
+        )
         fun createFrom(
             type: String,
             requestData: Bundle,
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
index 397cde3..9eb1c0d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
@@ -256,7 +256,7 @@
     ): android.credentials.GetCredentialRequest {
         val builder =
             android.credentials.GetCredentialRequest.Builder(
-                GetCredentialRequest.toRequestDataBundle(request)
+                GetCredentialRequest.getRequestMetadataBundle(request)
             )
         request.credentialOptions.forEach {
             builder.addCredentialOption(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
index 786acbb..5f49e5e 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
@@ -18,7 +18,8 @@
 
 import android.content.ComponentName
 import android.os.Bundle
-import androidx.annotation.RestrictTo
+import androidx.annotation.Discouraged
+import androidx.annotation.RequiresApi
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
@@ -168,7 +169,7 @@
         }
     }
 
-    internal companion object {
+    companion object {
         internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
             "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
         private const val BUNDLE_KEY_PREFER_IDENTITY_DOC_UI =
@@ -176,9 +177,15 @@
         private const val BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME =
             "androidx.credentials.BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME"
 
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        /**
+         * Returns the request metadata as a `Bundle`.
+         *
+         * Note: this is not the equivalent of the complete request itself. For example, it does not
+         * include the request's `credentialOptions` or `origin`.
+         */
         @JvmStatic
-        fun toRequestDataBundle(request: GetCredentialRequest): Bundle {
+        @Discouraged("It should only be used by OEM services and library groups")
+        fun getRequestMetadataBundle(request: GetCredentialRequest): Bundle {
             val bundle = Bundle()
             bundle.putBoolean(BUNDLE_KEY_PREFER_IDENTITY_DOC_UI, request.preferIdentityDocUi)
             bundle.putBoolean(
@@ -192,20 +199,49 @@
             return bundle
         }
 
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        /**
+         * Parses the [request] into an instance of [GetCredentialRequest].
+         *
+         * @param request the framework GetCredentialRequest object
+         */
+        @RequiresApi(34)
         @JvmStatic
+        @Discouraged(
+            "It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest"
+        )
+        fun createFrom(request: android.credentials.GetCredentialRequest): GetCredentialRequest {
+            return createFrom(
+                request.credentialOptions.map { CredentialOption.createFrom(it) },
+                request.origin,
+                request.data
+            )
+        }
+
+        /**
+         * Parses the raw data into an instance of [GetCredentialRequest].
+         *
+         * @param credentialOptions matches [GetCredentialRequest.credentialOptions]
+         * @param origin matches [GetCredentialRequest.origin]
+         * @param metadata request metadata serialized as a Bundle using [getRequestMetadataBundle]
+         */
+        @JvmStatic
+        @Discouraged(
+            "It is recommended to construct a GetCredentialRequest by directly instantiating a GetCredentialRequest"
+        )
         fun createFrom(
             credentialOptions: List<CredentialOption>,
             origin: String?,
-            data: Bundle
+            metadata: Bundle
         ): GetCredentialRequest {
             try {
-                val preferIdentityDocUi = data.getBoolean(BUNDLE_KEY_PREFER_IDENTITY_DOC_UI)
+                val preferIdentityDocUi = metadata.getBoolean(BUNDLE_KEY_PREFER_IDENTITY_DOC_UI)
                 val preferImmediatelyAvailableCredentials =
-                    data.getBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                    metadata.getBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
                 @Suppress("DEPRECATION")
                 val preferUiBrandingComponentName =
-                    data.getParcelable<ComponentName>(BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME)
+                    metadata.getParcelable<ComponentName>(
+                        BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME
+                    )
                 val getCredentialBuilder =
                     Builder()
                         .setCredentialOptions(credentialOptions)
diff --git a/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt b/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt
index bea36df..a109d8a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt
@@ -45,7 +45,7 @@
         ): android.credentials.GetCredentialRequest {
             val builder =
                 android.credentials.GetCredentialRequest.Builder(
-                    GetCredentialRequest.toRequestDataBundle(request)
+                    GetCredentialRequest.getRequestMetadataBundle(request)
                 )
             request.credentialOptions.forEach {
                 builder.addCredentialOption(
diff --git a/development/studio/studio.vmoptions b/development/studio/studio.vmoptions
index 5df7e92..c70257b 100644
--- a/development/studio/studio.vmoptions
+++ b/development/studio/studio.vmoptions
@@ -4,7 +4,7 @@
 # b/279181712
 -Djdk.attach.allowAttachSelf=true
 # b/297379481
--Dprofiler.trace.open.mode.web=false # disabling until we're on a build that contains ag/27303635
+-Dprofiler.trace.open.mode.web=true
 # https://github.com/google/google-java-format#intellij-jre-config
 --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
 --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
diff --git a/docs/api_guidelines/annotations.md b/docs/api_guidelines/annotations.md
index f7e9ca5..fd9af8d 100644
--- a/docs/api_guidelines/annotations.md
+++ b/docs/api_guidelines/annotations.md
@@ -255,7 +255,7 @@
 (`private`, `package`, or `public`) and is unrelated to hiding.
 
 For more, read the section in
-[Android API Council Guidelines](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#no-public-typedefs)
+[Android API Council Guidelines](https://android.googlesource.com/platform/developers/docs/+/refs/heads/main/api-guidelines/framework.md#framework-hide-typedefs)
 
 #### `*current.txt` File Explanation {#currenttxt}
 
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
index 67dc3c2..bd7027c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
@@ -24,7 +24,7 @@
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"ЕЖА І НАПОІ"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"ПАДАРОЖЖЫ І МЕСЦЫ"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"ДЗЕЯННІ І ПАДЗЕІ"</string>
-    <string name="emoji_category_objects" msgid="6106115586332708067">"АБ\'ЕКТЫ"</string>
+    <string name="emoji_category_objects" msgid="6106115586332708067">"АБ’ЕКТЫ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СІМВАЛЫ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"СЦЯГІ"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Няма даступных эмодзі"</string>
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index a625a7b..821aaee 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -391,7 +391,7 @@
     // https://issuetracker.google.com/342697059
     @Test
     @LargeTest
-    @SdkSuppress(minSdkVersion = 22)
+    @SdkSuppress(minSdkVersion = 22) // Parsing the large image causes OOM on API 21 FTL emulators.
     public void testWebpWithoutExifHeight8192px() throws Throwable {
         File imageFile =
                 copyFromResourceToFile(
diff --git a/glance/glance-appwidget-preview/build.gradle b/glance/glance-appwidget-preview/build.gradle
index 25f53bf..7211a90 100644
--- a/glance/glance-appwidget-preview/build.gradle
+++ b/glance/glance-appwidget-preview/build.gradle
@@ -51,6 +51,7 @@
 
 android {
     namespace "androidx.glance.appwidget.preview"
+    compileSdk 35
 }
 
 androidx {
diff --git a/glance/glance-appwidget-testing/build.gradle b/glance/glance-appwidget-testing/build.gradle
index 4859305..f8a6763 100644
--- a/glance/glance-appwidget-testing/build.gradle
+++ b/glance/glance-appwidget-testing/build.gradle
@@ -56,6 +56,7 @@
 
     defaultConfig {
         minSdkVersion 23
+        compileSdk 35
     }
     namespace "androidx.glance.appwidget.testing"
     // TODO(b/313699418): need to update compose.runtime version to 1.6.0+ in glance-appwidget
diff --git a/glance/glance-appwidget-testing/samples/build.gradle b/glance/glance-appwidget-testing/samples/build.gradle
index f07b53e..8e24f74 100644
--- a/glance/glance-appwidget-testing/samples/build.gradle
+++ b/glance/glance-appwidget-testing/samples/build.gradle
@@ -54,6 +54,7 @@
 android {
     defaultConfig {
         minSdkVersion 23
+        compileSdk 35
     }
     namespace "androidx.glance.appwidget.testing.samples"
 }
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index cdb829a..7558274 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -104,6 +104,8 @@
     }
     // TODO(b/313699418): need to update compose.runtime version to 1.6.0+
     experimentalProperties["android.lint.useK2Uast"] = false
+
+    compileSdk 35
 }
 
 androidx {
diff --git a/glance/glance-appwidget/integration-tests/demos/build.gradle b/glance/glance-appwidget/integration-tests/demos/build.gradle
index 448265d8..842fa90 100644
--- a/glance/glance-appwidget/integration-tests/demos/build.gradle
+++ b/glance/glance-appwidget/integration-tests/demos/build.gradle
@@ -44,4 +44,5 @@
     namespace "androidx.glance.appwidget.demos"
     // TODO(b/313699418): need to update compose.runtime version to 1.6.0+ in glance-appwidget
     experimentalProperties["android.lint.useK2Uast"] = false
+    compileSdk 35
 }
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark-target/build.gradle b/glance/glance-appwidget/integration-tests/macrobenchmark-target/build.gradle
index f3f0f5d..3c839b8 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark-target/build.gradle
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark-target/build.gradle
@@ -30,6 +30,7 @@
 
 android {
     namespace "androidx.glance.appwidget.macrobenchmark.target"
+    compileSdk 35
     buildTypes {
         release {
             minifyEnabled true
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle b/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
index cfa7f61..3705bec 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
@@ -23,6 +23,7 @@
 android {
     defaultConfig {
         minSdkVersion 23
+        compileSdk 35
     }
     namespace "androidx.glance.appwidget.macrobenchmark"
     targetProjectPath = ":glance:glance-appwidget:integration-tests:macrobenchmark-target"
diff --git a/glance/glance-appwidget/samples/build.gradle b/glance/glance-appwidget/samples/build.gradle
index 01f1cd7..15791e6 100644
--- a/glance/glance-appwidget/samples/build.gradle
+++ b/glance/glance-appwidget/samples/build.gradle
@@ -52,4 +52,5 @@
 
 android {
     namespace "androidx.glance.appwidget.samples"
+    compileSdk 35
 }
diff --git a/glance/glance-template/build.gradle b/glance/glance-template/build.gradle
index 89b21c6..90415bf 100644
--- a/glance/glance-template/build.gradle
+++ b/glance/glance-template/build.gradle
@@ -74,6 +74,7 @@
     testOptions.unitTests.includeAndroidResources = true
     resourcePrefix "glance_template_"
     namespace "androidx.glance.template"
+    compileSdk 35
 }
 
 androidx {
diff --git a/glance/glance-template/integration-tests/template-demos/build.gradle b/glance/glance-template/integration-tests/template-demos/build.gradle
index 3e789c4..31359b9 100644
--- a/glance/glance-template/integration-tests/template-demos/build.gradle
+++ b/glance/glance-template/integration-tests/template-demos/build.gradle
@@ -44,4 +44,5 @@
 
 android {
     namespace "androidx.glance.appwidget.template.demos"
+    compileSdk 35
 }
diff --git a/glance/glance-wear-tiles/build.gradle b/glance/glance-wear-tiles/build.gradle
index 6a4bc14..e105490 100644
--- a/glance/glance-wear-tiles/build.gradle
+++ b/glance/glance-wear-tiles/build.gradle
@@ -73,6 +73,7 @@
 android {
     defaultConfig {
         minSdkVersion 26
+        compileSdk 35
     }
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
diff --git a/glance/glance-wear-tiles/integration-tests/demos/build.gradle b/glance/glance-wear-tiles/integration-tests/demos/build.gradle
index 50f1047..dfb13e0 100644
--- a/glance/glance-wear-tiles/integration-tests/demos/build.gradle
+++ b/glance/glance-wear-tiles/integration-tests/demos/build.gradle
@@ -35,6 +35,7 @@
 android {
     defaultConfig {
         minSdkVersion 26
+        compileSdk 35
     }
     namespace "androidx.glance.wear.tiles.demos"
-}
\ No newline at end of file
+}
diff --git a/glance/glance-wear-tiles/integration-tests/template-demos/build.gradle b/glance/glance-wear-tiles/integration-tests/template-demos/build.gradle
index 99919d7..6c42393 100644
--- a/glance/glance-wear-tiles/integration-tests/template-demos/build.gradle
+++ b/glance/glance-wear-tiles/integration-tests/template-demos/build.gradle
@@ -33,6 +33,7 @@
 android {
     defaultConfig {
         minSdkVersion 26
+        compileSdk 35
     }
     namespace "androidx.glance.wear.tiles.template.demos"
 }
diff --git a/gradle.properties b/gradle.properties
index 4cfe031..ebd54e7 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -44,6 +44,7 @@
 android.suppressUnsupportedCompileSdk=35
 
 androidx.compileSdk=34
+androidx.latestStableCompileSdk=35
 androidx.targetSdkVersion=34
 androidx.allowCustomCompileSdk=true
 androidx.includeOptionalProjects=false
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index c6b9195..f5a3628 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -621,6 +621,14 @@
             <sha256 value="bc787098a208e6bdc8d93720a162bbea01df1b26394d1c1ef5ec523b1d604e8b" origin="Generated by Gradle" reason="Artifact is not signed"/>
          </artifact>
       </component>
+      <component group="com.google.android.apps.common.testing.accessibility.framework" name="accessibility-test-framework" version="4.1.1">
+         <artifact name="accessibility-test-framework-4.1.1.aar">
+            <sha256 value="f19c550163f277a2aebe7e002a7988c3544c693ee29dd7ac4d4747c2971e2c45" origin="Generated by Gradle" reason="Artifact is not signed. b/359566848"/>
+         </artifact>
+         <artifact name="accessibility-test-framework-4.1.1.pom">
+            <sha256 value="c038e90864935f4b4fe50ece742c41ee33ca490149f780328929811e8548c1b6" origin="Generated by Gradle" reason="Artifact is not signed. b/359566848"/>
+         </artifact>
+      </component>
       <component group="com.google.android.libraries.identity.googleid" name="googleid" version="0.0.2">
          <artifact name="googleid-0.0.2.aar">
             <sha256 value="ae69047587928df1ff82ce71e9f2ec40a777270f04f4a6d1f66241944961b682" origin="Generated by Gradle" reason="Artifact is not signed"/>
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3164a58..9bd15e6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.9-bin.zip
-distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
+distributionUrl=../../../../tools/external/gradle/gradle-8.10-bin.zip
+distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 8552ee4..99460ea 100755
--- a/gradlew
+++ b/gradlew
@@ -16,6 +16,9 @@
     OUT_DIR="$(cd $OUT_DIR && pwd -P)"
     export GRADLE_USER_HOME="$OUT_DIR/.gradle"
     export TMPDIR="$OUT_DIR/tmp"
+elif [[ $SCRIPT_PATH == /google/cog/* ]] ; then
+    export OUT_DIR="$HOME/androidxout"
+    export GRADLE_USER_HOME=~/.gradle
 else
     CHECKOUT_ROOT="$(cd $SCRIPT_PATH/../.. && pwd -P)"
     export OUT_DIR="$CHECKOUT_ROOT/out"
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
index 179b70c..3f41299 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/CanvasFrontBufferedRendererTest.kt
@@ -101,9 +101,7 @@
             }
         ) { scenario, renderer, surfaceView ->
             renderLatch.set(CountDownLatch(1))
-            scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-                renderer.renderFrontBufferedLayer(Any())
-            }
+            scenario.onActivity { renderer.renderFrontBufferedLayer(Any()) }
             Assert.assertTrue(renderLatch.get()!!.await(3000, TimeUnit.MILLISECONDS))
 
             val coords = IntArray(2)
@@ -174,15 +172,13 @@
                     // NO-OP
                 }
             }
-        ) { scenario, _, surfaceView ->
+        ) { _, _, surfaceView ->
             val paramLatch = CountDownLatch(1)
             surfaceView.post {
                 surfaceView.layoutParams = FrameLayout.LayoutParams(width, height)
                 paramLatch.countDown()
             }
             paramLatch.await()
-
-            scenario.moveToState(Lifecycle.State.RESUMED)
         }
     }
 
@@ -232,7 +228,7 @@
             }
         ) { scenario, renderer, surfaceView ->
             renderLatch.set(CountDownLatch(1))
-            scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+            scenario.onActivity {
                 renderer.renderFrontBufferedLayer(Any())
                 renderer.commit()
             }
@@ -516,7 +512,7 @@
             }
         ) { scenario, renderer, surfaceView ->
             renderLatch.set(CountDownLatch(2))
-            scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+            scenario.onActivity {
                 with(renderer) {
                     renderFrontBufferedLayer(Color.BLUE)
                     commit()
@@ -605,14 +601,10 @@
         var renderer: CanvasFrontBufferedRenderer<Float>? = null
         var surfaceView: SurfaceView? = null
         try {
-            val scenario =
-                ActivityScenario.launch(SurfaceViewTestActivity::class.java)
-                    .moveToState(Lifecycle.State.CREATED)
-                    .onActivity {
-                        surfaceView = it.getSurfaceView().apply { setZOrderOnTop(true) }
-                        renderer = CanvasFrontBufferedRenderer(surfaceView!!, callbacks)
-                    }
-            scenario.moveToState(Lifecycle.State.RESUMED)
+            ActivityScenario.launch(SurfaceViewTestActivity::class.java).onActivity {
+                surfaceView = it.getSurfaceView().apply { setZOrderOnTop(true) }
+                renderer = CanvasFrontBufferedRenderer(surfaceView!!, callbacks)
+            }
 
             assertTrue(firstRenderLatch.await(3000, TimeUnit.MILLISECONDS))
 
@@ -1424,14 +1416,11 @@
         var scenario: ActivityScenario<SurfaceViewTestActivity>? = null
         try {
             scenario =
-                ActivityScenario.launch(SurfaceViewTestActivity::class.java)
-                    .moveToState(Lifecycle.State.CREATED)
-                    .onActivity {
-                        surfaceView = it.getSurfaceView()
-                        renderer = CanvasFrontBufferedRenderer<T>(surfaceView!!, wrappedCallbacks)
-                        it.setOnDestroyCallback { destroyLatch.countDown() }
-                    }
-            scenario.moveToState(Lifecycle.State.RESUMED)
+                ActivityScenario.launch(SurfaceViewTestActivity::class.java).onActivity {
+                    surfaceView = it.getSurfaceView()
+                    renderer = CanvasFrontBufferedRenderer<T>(surfaceView!!, wrappedCallbacks)
+                    it.setOnDestroyCallback { destroyLatch.countDown() }
+                }
             assertTrue(firstRenderLatch.await(3000, TimeUnit.MILLISECONDS))
             block(scenario, renderer!!, surfaceView!!)
         } finally {
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 4fabbb6..9fd7f1f 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -9,7 +9,7 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public androidx.health.connect.client.HealthConnectFeatures getFeatures();
+    method public default androidx.health.connect.client.HealthConnectFeatures getFeatures();
     method public static android.content.Intent getHealthConnectManageDataIntent(android.content.Context context);
     method public static android.content.Intent getHealthConnectManageDataIntent(android.content.Context context, optional String providerPackageName);
     method public static String getHealthConnectSettingsAction();
@@ -23,7 +23,7 @@
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecords(androidx.health.connect.client.request.ReadRecordsRequest<T> request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordsResponse<T>>);
     method public suspend Object? updateRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public static String ACTION_HEALTH_CONNECT_SETTINGS;
-    property public abstract androidx.health.connect.client.HealthConnectFeatures features;
+    property @SuppressCompatibility @androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi public default androidx.health.connect.client.HealthConnectFeatures features;
     property public abstract androidx.health.connect.client.PermissionController permissionController;
     field public static final androidx.health.connect.client.HealthConnectClient.Companion Companion;
     field public static final int SDK_AVAILABLE = 3; // 0x3
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 3756d80..eecd9a63 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -9,7 +9,7 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public androidx.health.connect.client.HealthConnectFeatures getFeatures();
+    method public default androidx.health.connect.client.HealthConnectFeatures getFeatures();
     method public static android.content.Intent getHealthConnectManageDataIntent(android.content.Context context);
     method public static android.content.Intent getHealthConnectManageDataIntent(android.content.Context context, optional String providerPackageName);
     method public static String getHealthConnectSettingsAction();
@@ -23,7 +23,7 @@
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecords(androidx.health.connect.client.request.ReadRecordsRequest<T> request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordsResponse<T>>);
     method public suspend Object? updateRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public static String ACTION_HEALTH_CONNECT_SETTINGS;
-    property public abstract androidx.health.connect.client.HealthConnectFeatures features;
+    property @SuppressCompatibility @androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi public default androidx.health.connect.client.HealthConnectFeatures features;
     property public abstract androidx.health.connect.client.PermissionController permissionController;
     field public static final androidx.health.connect.client.HealthConnectClient.Companion Companion;
     field public static final int SDK_AVAILABLE = 3; // 0x3
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
index 6a51c31..d8c56c7 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
@@ -30,6 +30,7 @@
 import androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration
 import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
 import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
+import androidx.health.connect.client.feature.HealthConnectFeaturesUnavailableImpl
 import androidx.health.connect.client.impl.HealthConnectClientImpl
 import androidx.health.connect.client.impl.HealthConnectClientUpsideDownImpl
 import androidx.health.connect.client.records.Record
@@ -55,7 +56,9 @@
     val permissionController: PermissionController
 
     /** Access operations related to feature availability. */
-    @ExperimentalFeatureAvailabilityApi val features: HealthConnectFeatures
+    @ExperimentalFeatureAvailabilityApi
+    val features: HealthConnectFeatures
+        get() = HealthConnectFeaturesUnavailableImpl
 
     /**
      * Inserts one or more [Record] and returns newly assigned
diff --git a/health/connect/connect-testing/api/current.txt b/health/connect/connect-testing/api/current.txt
index 9834c8e..a6ddad6 100644
--- a/health/connect/connect-testing/api/current.txt
+++ b/health/connect/connect-testing/api/current.txt
@@ -21,7 +21,6 @@
     method public void expireToken(String token);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public androidx.health.connect.client.HealthConnectFeatures getFeatures();
     method public androidx.health.connect.client.testing.FakeHealthConnectClientOverrides getOverrides();
     method public int getPageSizeGetChanges();
     method public androidx.health.connect.client.PermissionController getPermissionController();
diff --git a/health/connect/connect-testing/api/restricted_current.txt b/health/connect/connect-testing/api/restricted_current.txt
index 9834c8e..a6ddad6 100644
--- a/health/connect/connect-testing/api/restricted_current.txt
+++ b/health/connect/connect-testing/api/restricted_current.txt
@@ -21,7 +21,6 @@
     method public void expireToken(String token);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public androidx.health.connect.client.HealthConnectFeatures getFeatures();
     method public androidx.health.connect.client.testing.FakeHealthConnectClientOverrides getOverrides();
     method public int getPageSizeGetChanges();
     method public androidx.health.connect.client.PermissionController getPermissionController();
diff --git a/libraryversions.toml b/libraryversions.toml
index c7e0f8c..fa7ebba 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,7 +1,7 @@
 [versions]
 ACTIVITY = "1.10.0-beta01"
-ANNOTATION = "1.9.0-alpha01"
-ANNOTATION_EXPERIMENTAL = "1.4.0-rc01"
+ANNOTATION = "1.9.0-alpha02"
+ANNOTATION_EXPERIMENTAL = "1.5.0-alpha01"
 APPCOMPAT = "1.8.0-alpha01"
 APPSEARCH = "1.1.0-alpha04"
 ARCH_CORE = "2.3.0-alpha01"
@@ -17,7 +17,7 @@
 CAMERA_TESTING = "1.0.0-alpha01"
 CAMERA_VIEWFINDER = "1.4.0-alpha08"
 CARDVIEW = "1.1.0-alpha01"
-CAR_APP = "1.7.0-beta01"
+CAR_APP = "1.7.0-beta02"
 COLLECTION = "1.5.0-alpha01"
 COMPOSE = "1.8.0-alpha01"
 COMPOSE_MATERIAL3 = "1.4.0-alpha01"
@@ -41,7 +41,7 @@
 CORE_REMOTEVIEWS = "1.1.0-rc01"
 CORE_ROLE = "1.2.0-alpha01"
 CORE_SPLASHSCREEN = "1.2.0-alpha01"
-CORE_TELECOM = "1.0.0-alpha13"
+CORE_TELECOM = "1.0.0-alpha4"
 CORE_UWB = "1.0.0-alpha08"
 CREDENTIALS = "1.5.0-alpha04"
 CREDENTIALS_E2EE_QUARANTINE = "1.0.0-alpha02"
@@ -96,10 +96,10 @@
 MEDIA = "1.7.0-rc01"
 MEDIAROUTER = "1.8.0-alpha01"
 METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-beta07"
+NAVIGATION = "2.8.0-rc01"
 PAGING = "3.4.0-alpha01"
 PALETTE = "1.1.0-alpha01"
-PDF = "1.0.0-alpha01"
+PDF = "1.0.0-alpha02"
 PERCENTLAYOUT = "1.1.0-alpha01"
 PREFERENCE = "1.3.0-alpha01"
 PRINT = "1.1.0-beta01"
@@ -109,13 +109,13 @@
 PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha14"
 PRIVACYSANDBOX_TOOLS = "1.0.0-alpha09"
 PRIVACYSANDBOX_UI = "1.0.0-alpha09"
-PROFILEINSTALLER = "1.4.0-alpha02"
+PROFILEINSTALLER = "1.4.0-beta01"
 RECOMMENDATION = "1.1.0-alpha01"
 RECYCLERVIEW = "1.4.0-beta01"
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.7.0-alpha06"
+ROOM = "2.7.0-alpha07"
 SAFEPARCEL = "1.0.0-alpha01"
 SAVEDSTATE = "1.3.0-alpha01"
 SECURITY = "1.1.0-alpha07"
@@ -131,10 +131,10 @@
 SLICE_BUILDERS_KTX = "1.0.0-alpha09"
 SLICE_REMOTECALLBACK = "1.0.0-alpha01"
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.5.0-alpha06"
+SQLITE = "2.5.0-alpha07"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
 STABLE_AIDL = "1.0.0-alpha01"
-STARTUP = "1.2.0-alpha03"
+STARTUP = "1.2.0-beta01"
 SWIPEREFRESHLAYOUT = "1.2.0-alpha01"
 TESTEXT = "1.0.0-alpha03"
 TESTSCREENSHOT = "1.0.0-alpha01"
@@ -143,9 +143,9 @@
 TRACING = "1.3.0-alpha02"
 TRACING_PERFETTO = "1.0.0"
 TRANSITION = "1.5.0-rc01"
-TV = "1.0.0-alpha11"
+TV = "1.0.0-alpha12"
 TVPROVIDER = "1.1.0-alpha02"
-TV_MATERIAL = "1.0.0-rc01"
+TV_MATERIAL = "1.1.0-alpha01"
 VECTORDRAWABLE = "1.2.0"
 VECTORDRAWABLE_ANIMATED = "1.2.0"
 VECTORDRAWABLE_SEEKABLE = "1.0.0"
diff --git a/lifecycle/lifecycle-runtime/proguard-rules.pro b/lifecycle/lifecycle-runtime/proguard-rules.pro
index 95192c1..4335578 100644
--- a/lifecycle/lifecycle-runtime/proguard-rules.pro
+++ b/lifecycle/lifecycle-runtime/proguard-rules.pro
@@ -15,6 +15,12 @@
     @androidx.lifecycle.OnLifecycleEvent *;
 }
 
+# The deprecated `android.app.Fragment` creates `Fragment` instances using reflection.
+# See: b/338958225, b/341537875
+-keepclasseswithmembers,allowobfuscation public class androidx.lifecycle.ReportFragment {
+    public <init>();
+}
+
 # this rule is need to work properly when app is compiled with api 28, see b/142778206
 # Also this rule prevents registerIn from being inlined.
--keepclassmembers class androidx.lifecycle.ReportFragment$LifecycleCallbacks { *; }
\ No newline at end of file
+-keepclassmembers class androidx.lifecycle.ReportFragment$LifecycleCallbacks { *; }
diff --git a/mediarouter/mediarouter/src/androidTest/AndroidManifest.xml b/mediarouter/mediarouter/src/androidTest/AndroidManifest.xml
index 6460082..7961fb1 100644
--- a/mediarouter/mediarouter/src/androidTest/AndroidManifest.xml
+++ b/mediarouter/mediarouter/src/androidTest/AndroidManifest.xml
@@ -35,6 +35,13 @@
                 <action android:name="android.media.MediaRouteProviderService" />
             </intent-filter>
         </service>
+        <service
+            android:name="androidx.mediarouter.media.StubDynamicMediaRouteProviderService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.MediaRouteProviderService" />
+            </intent-filter>
+        </service>
 
         <receiver android:name="androidx.media.session.MediaButtonReceiver"
             android:exported="true">
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterDynamicProviderTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterDynamicProviderTest.java
new file mode 100644
index 0000000..f1a83ab
--- /dev/null
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterDynamicProviderTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.mediarouter.media;
+
+import static androidx.mediarouter.media.StubDynamicMediaRouteProviderService.ROUTE_ID_GROUP;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.ConditionVariable;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.mediarouter.testing.MediaRouterTestHelper;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Test for {@link MediaRouter} functionality around routes from a provider that supports {@link
+ * MediaRouteProviderDescriptor#supportsDynamicGroupRoute() dynamic group routes}.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Dynamic groups require API 30+.
+@SmallTest
+public final class MediaRouterDynamicProviderTest {
+
+    private Context mContext;
+    private MediaRouter mRouter;
+
+    @Before
+    public void setUp() {
+        getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            mContext = getApplicationContext();
+                            mRouter = MediaRouter.getInstance(mContext);
+                        });
+    }
+
+    @After
+    public void tearDown() {
+        getInstrumentation().runOnMainSync(MediaRouterTestHelper::resetMediaRouter);
+    }
+
+    // Tests.
+
+    @Test()
+    public void selectDynamicRoute_doesNotMarkMemberAsSelected() {
+        MediaRouteSelector selector =
+                new MediaRouteSelector.Builder()
+                        .addControlCategory(
+                                StubDynamicMediaRouteProviderService.CATEGORY_DYNAMIC_PROVIDER_TEST)
+                        .build();
+        MediaRouterCallbackImpl callback = new MediaRouterCallbackImpl();
+        getInstrumentation()
+                .runOnMainSync(
+                        () ->
+                                mRouter.addCallback(
+                                        selector,
+                                        callback,
+                                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN));
+        Map<String, MediaRouter.RouteInfo> routeSnapshot =
+                callback.waitForRoutes(
+                        StubDynamicMediaRouteProviderService.ROUTE_ID_1,
+                        StubDynamicMediaRouteProviderService.ROUTE_ID_1);
+        MediaRouter.RouteInfo routeToSelect =
+                routeSnapshot.get(StubDynamicMediaRouteProviderService.ROUTE_ID_1);
+        Objects.requireNonNull(routeToSelect);
+        MediaRouter.RouteInfo newSelectedRoute = callback.selectAndWaitForOnSelected(routeToSelect);
+        assertEquals(ROUTE_ID_GROUP, newSelectedRoute.getDescriptorId());
+        assertFalse(runBlockingOnMainThreadWithResult(routeToSelect::isSelected));
+        assertTrue(runBlockingOnMainThreadWithResult(newSelectedRoute::isSelected));
+    }
+
+    // Internal methods.
+
+    private Map<String, MediaRouter.RouteInfo> getCurrentRoutesAsMap() {
+        Supplier<Map<String, MediaRouter.RouteInfo>> supplier =
+                () -> {
+                    Map<String, MediaRouter.RouteInfo> routeIds = new HashMap<>();
+                    for (MediaRouter.RouteInfo route : mRouter.getRoutes()) {
+                        routeIds.put(route.getDescriptorId(), route);
+                    }
+                    return routeIds;
+                };
+        return runBlockingOnMainThreadWithResult(supplier);
+    }
+
+    @SuppressWarnings("unchecked") // Allows us to pull a generic result out of runOnMainSync.
+    private <Result> Result runBlockingOnMainThreadWithResult(Supplier<Result> supplier) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            return supplier.get();
+        } else {
+            Result[] resultHolder = (Result[]) new Object[1];
+            getInstrumentation().runOnMainSync(() -> resultHolder[0] = supplier.get());
+            return resultHolder[0];
+        }
+    }
+
+    // Internal classes and interfaces.
+
+    // Equivalent to java.util.function.Supplier, except it's available before API 24.
+    private interface Supplier<Result> {
+
+        Result get();
+    }
+
+    private class MediaRouterCallbackImpl extends MediaRouter.Callback {
+
+        private final ConditionVariable mPendingRoutesConditionVariable = new ConditionVariable();
+        private final Set<String> mRouteIdsPending = new HashSet<>();
+        private final ConditionVariable mSelectedRouteChangeConditionVariable =
+                new ConditionVariable(/* state= */ true);
+
+        public Map<String, MediaRouter.RouteInfo> waitForRoutes(String... routeIds) {
+            Set<String> routesIdsSet = new HashSet<>(Arrays.asList(routeIds));
+            getInstrumentation()
+                    .runOnMainSync(
+                            () -> {
+                                Map<String, MediaRouter.RouteInfo> routes = getCurrentRoutesAsMap();
+                                if (!routes.keySet().containsAll(routesIdsSet)) {
+                                    mPendingRoutesConditionVariable.close();
+                                    mRouteIdsPending.clear();
+                                    mRouteIdsPending.addAll(routesIdsSet);
+                                } else {
+                                    mPendingRoutesConditionVariable.open();
+                                }
+                            });
+            mPendingRoutesConditionVariable.block();
+            return getCurrentRoutesAsMap();
+        }
+
+        public MediaRouter.RouteInfo selectAndWaitForOnSelected(
+                MediaRouter.RouteInfo routeToSelect) {
+            mSelectedRouteChangeConditionVariable.close();
+            getInstrumentation().runOnMainSync(routeToSelect::select);
+            mSelectedRouteChangeConditionVariable.block();
+            return runBlockingOnMainThreadWithResult(() -> mRouter.getSelectedRoute());
+        }
+
+        @Override
+        public void onRouteSelected(
+                @NonNull MediaRouter router,
+                @NonNull MediaRouter.RouteInfo selectedRoute,
+                int reason,
+                @NonNull MediaRouter.RouteInfo requestedRoute) {
+            mSelectedRouteChangeConditionVariable.open();
+        }
+
+        @Override
+        public void onRouteAdded(
+                @NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route) {
+            if (getCurrentRoutesAsMap().keySet().containsAll(mRouteIdsPending)) {
+                mPendingRoutesConditionVariable.open();
+            }
+        }
+    }
+}
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubDynamicMediaRouteProviderService.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubDynamicMediaRouteProviderService.java
new file mode 100644
index 0000000..9b8cb32
--- /dev/null
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubDynamicMediaRouteProviderService.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.mediarouter.media;
+
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.ArrayMap;
+import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Stub {@link MediaRouteProviderService} implementation that supports dynamic groups. */
+public final class StubDynamicMediaRouteProviderService extends MediaRouteProviderService {
+    public static final String CATEGORY_DYNAMIC_PROVIDER_TEST =
+            "androidx.mediarouter.media.CATEGORY_DYNAMIC_PROVIDER_TEST";
+
+    public static final String ROUTE_ID_GROUP = "route_id_group";
+    public static final String ROUTE_NAME_GROUP = "Group route name";
+    public static final String ROUTE_ID_1 = "route_id1";
+    public static final String ROUTE_NAME_1 = "Sample Route 1";
+    public static final String ROUTE_ID_2 = "route_id2";
+    public static final String ROUTE_NAME_2 = "Sample Route 2";
+
+    private static final List<IntentFilter> CONTROL_FILTERS_TEST = new ArrayList<>();
+
+    static {
+        IntentFilter filter = new IntentFilter();
+        filter.addCategory(CATEGORY_DYNAMIC_PROVIDER_TEST);
+        CONTROL_FILTERS_TEST.add(filter);
+    }
+
+    @Override
+    public MediaRouteProvider onCreateMediaRouteProvider() {
+        return new Provider(/* context= */ this);
+    }
+
+    private static final class Provider extends MediaRouteProvider {
+        private final Map<String, MediaRouteDescriptor> mRoutes = new ArrayMap<>();
+        private final Map<String, StubRouteController> mControllers = new ArrayMap<>();
+        private final MediaRouteDescriptor mGroupDescriptor;
+        private final Set<String> mCurrentSelectedRouteIds = new HashSet<>();
+        private boolean mCurrentlyScanning = false;
+        @Nullable private DynamicGroupRouteController mGroupController;
+
+        Provider(@NonNull Context context) {
+            super(context);
+            mGroupDescriptor =
+                    new MediaRouteDescriptor.Builder(ROUTE_ID_GROUP, ROUTE_NAME_GROUP)
+                            .addControlFilters(CONTROL_FILTERS_TEST)
+                            .build();
+            MediaRouteDescriptor route1 =
+                    new MediaRouteDescriptor.Builder(ROUTE_ID_1, ROUTE_NAME_1)
+                            .addControlFilters(CONTROL_FILTERS_TEST)
+                            .build();
+            mRoutes.put(route1.getId(), route1);
+            MediaRouteDescriptor route2 =
+                    new MediaRouteDescriptor.Builder(ROUTE_ID_2, ROUTE_NAME_2)
+                            .addControlFilters(CONTROL_FILTERS_TEST)
+                            .build();
+            mRoutes.put(route2.getId(), route2);
+        }
+
+        // MediaRouteProvider implementation.
+
+        @Override
+        public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
+            mCurrentlyScanning =
+                    request != null
+                            && request.isActiveScan()
+                            && request.getSelector()
+                                    .hasControlCategory(CATEGORY_DYNAMIC_PROVIDER_TEST);
+            publishProviderState();
+        }
+
+        @Override
+        public RouteController onCreateRouteController(@NonNull String routeId) {
+            StubRouteController newController = new StubRouteController(routeId);
+            mControllers.put(routeId, newController);
+            return newController;
+        }
+
+        @Nullable
+        @Override
+        public DynamicGroupRouteController onCreateDynamicGroupRouteController(
+                @NonNull String initialMemberRouteId, @Nullable Bundle controlHints) {
+            mGroupController = new StubDynamicRouteController();
+            return mGroupController;
+        }
+
+        // Internal methods.
+
+        private void publishProviderState() {
+            MediaRouteProviderDescriptor.Builder providerDescriptor =
+                    new MediaRouteProviderDescriptor.Builder().setSupportsDynamicGroupRoute(true);
+            if (mCurrentlyScanning) {
+                providerDescriptor.addRoutes(mRoutes.values());
+            }
+            setDescriptor(providerDescriptor.build());
+        }
+
+        private Collection<DynamicRouteDescriptor> buildDynamicRouteDescriptors() {
+            ArrayList<DynamicRouteDescriptor> result = new ArrayList<>();
+            for (MediaRouteDescriptor route : mRoutes.values()) {
+                DynamicRouteDescriptor dynamicDescriptor =
+                        new DynamicRouteDescriptor.Builder(route)
+                                .setSelectionState(
+                                        mCurrentSelectedRouteIds.contains(route.getId())
+                                                ? DynamicRouteDescriptor.SELECTED
+                                                : DynamicRouteDescriptor.UNSELECTED)
+                                .build();
+                result.add(dynamicDescriptor);
+            }
+            return result;
+        }
+
+        // Internal classes.
+
+        private class StubRouteController extends RouteController {
+            private final String mRouteId;
+
+            private StubRouteController(String routeId) {
+                mRouteId = routeId;
+            }
+
+            @Override
+            public void onSelect() {
+                mRoutes.put(
+                        mRouteId,
+                        new MediaRouteDescriptor.Builder(mRoutes.get(mRouteId))
+                                .setConnectionState(
+                                        MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED)
+                                .build());
+                publishProviderState();
+            }
+
+            @Override
+            public void onUnselect(int reason) {
+                mRoutes.put(
+                        mRouteId,
+                        new MediaRouteDescriptor.Builder(mRoutes.get(mRouteId))
+                                .setConnectionState(
+                                        MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED)
+                                .build());
+                publishProviderState();
+            }
+        }
+
+        private class StubDynamicRouteController extends DynamicGroupRouteController {
+
+            @Override
+            public void onSelect() {
+                publishState();
+            }
+
+            @Override
+            public void onUpdateMemberRoutes(@Nullable List<String> routeIds) {
+                mCurrentSelectedRouteIds.clear();
+                mCurrentSelectedRouteIds.addAll(routeIds);
+                publishState();
+            }
+
+            @Override
+            public void onAddMemberRoute(@NonNull String routeId) {
+                mCurrentSelectedRouteIds.add(routeId);
+                publishState();
+            }
+
+            @Override
+            public void onRemoveMemberRoute(@NonNull String routeId) {
+                mCurrentSelectedRouteIds.remove(routeId);
+                publishState();
+            }
+
+            private void publishState() {
+                notifyDynamicRoutesChanged(mGroupDescriptor, buildDynamicRouteDescriptors());
+            }
+        }
+    }
+}
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 5969262..9fa7d67 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -28,11 +28,11 @@
 
     implementation(libs.kotlinStdlib)
     api("androidx.activity:activity-compose:1.8.0")
-    api("androidx.compose.animation:animation:1.7.0-beta06")
-    implementation("androidx.compose.foundation:foundation-layout:1.7.0-beta06")
-    api("androidx.compose.runtime:runtime:1.7.0-beta06")
-    api("androidx.compose.runtime:runtime-saveable:1.7.0-beta06")
-    api("androidx.compose.ui:ui:1.7.0-beta06")
+    api("androidx.compose.animation:animation:1.7.0-rc01")
+    implementation("androidx.compose.foundation:foundation-layout:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime-saveable:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
     api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
     api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
     implementation(libs.kotlinSerializationCore)
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
index 4e97003..98eae1f 100644
--- a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
@@ -58,7 +58,8 @@
 
         val scenario =
             launchFragmentInContainer<MockPdfViewerFragment>(
-                themeResId = androidx.appcompat.R.style.Theme_AppCompat_DayNight_NoActionBar,
+                themeResId =
+                    com.google.android.material.R.style.Theme_Material3_DayNight_NoActionBar,
                 initialState = Lifecycle.State.INITIALIZED
             )
         scenario.moveToState(nextState)
@@ -109,6 +110,7 @@
         onView(withId(R.id.parent_pdf_container))
             .perform(selectionViewActions.longClickAndDragRight())
         onView(withId(R.id.parent_pdf_container)).check(selectionViewActions.stopHandleMoved())
+        scenario.close()
     }
 
     @Test
@@ -155,6 +157,7 @@
         onView(withId(R.id.close_btn)).perform(click())
         onView(withId(R.id.find_query_box))
             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
+        scenario.close()
     }
 
     @Test
@@ -188,6 +191,7 @@
         // Swipe actions
         onView(withId(R.id.parent_pdf_container)).perform(swipeUp())
         onView(withId(R.id.parent_pdf_container)).perform(swipeDown())
+        scenario.close()
     }
 
     @Test
@@ -214,6 +218,7 @@
                 "Incorrect exception returned ${fragment.documentError?.message}"
             )
         }
+        scenario.close()
     }
 
     companion object {
@@ -221,7 +226,7 @@
         private const val TEST_PROTECTED_DOCUMENT_FILE = "sample-protected.pdf"
         private const val TEST_CORRUPTED_DOCUMENT_FILE = "corrupted.pdf"
         private const val PROTECTED_DOCUMENT_PASSWORD = "abcd1234"
-        private const val DELAY_TIME_MS = 1000L
+        private const val DELAY_TIME_MS = 500L
         private const val SEARCH_QUERY = "ipsum"
     }
 }
diff --git a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
index 66ef0c3..5b4c550 100644
--- a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
+++ b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
@@ -147,7 +147,7 @@
     private var shouldRedrawOnDocumentLoaded = false
     private var isAnnotationIntentResolvable = false
     private var documentLoaded = false
-    private var isDocumentLoadedFirstTime = false
+    private var isSearchMenuAdjusted = false
 
     /**
      * The URI of the PDF document to display defaulting to `null`.
@@ -294,9 +294,8 @@
                         shouldRedrawOnDocumentLoaded = false
                     }
                     annotationButton?.let { button ->
-                        if (isDocumentLoadedFirstTime && isAnnotationIntentResolvable) {
+                        if ((savedInstanceState == null) && isAnnotationIntentResolvable) {
                             button.visibility = View.VISIBLE
-                            isDocumentLoadedFirstTime = false
                         }
                     }
                 },
@@ -304,12 +303,25 @@
             )
 
         setUpEditFab()
+        if (savedInstanceState != null) {
+            paginatedView?.isConfigurationChanged = true
+        }
 
-        // Need to adjust the view only after the layout phase is completed for the views to
-        // accurately calculate the height of the view
+        /**
+         * Need to adjust the view only after the layout phase is completed for the views to
+         * accurately calculate the height of the view. The condition for visibility and
+         * [isSearchMenuAdjusted] guarantees that the listener is only invoked once after layout
+         * change.
+         */
         findInFileView?.let { view ->
             view.viewTreeObserver?.addOnGlobalLayoutListener {
-                activity?.let { adjustInsetsForSearchMenu(view, requireActivity()) }
+                if (view.visibility == View.VISIBLE) {
+                    if (!isSearchMenuAdjusted) {
+                        activity?.let { adjustInsetsForSearchMenu(view, it) }
+                    } else {
+                        isSearchMenuAdjusted = false
+                    }
+                }
             }
         }
 
@@ -364,6 +376,8 @@
         findInFileView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
             bottomMargin = menuMargin
         }
+
+        isSearchMenuAdjusted = true
     }
 
     /** Called after this viewer enters the screen and becomes visible. */
@@ -467,6 +481,12 @@
                 val showAnnotationButton = state.getBoolean(KEY_SHOW_ANNOTATION)
                 isAnnotationIntentResolvable =
                     showAnnotationButton && findInFileView!!.visibility != View.VISIBLE
+                if (
+                    isAnnotationIntentResolvable &&
+                        state.getBoolean(KEY_ANNOTATION_BUTTON_VISIBILITY)
+                ) {
+                    annotationButton?.visibility = View.VISIBLE
+                }
             }
         }
 
@@ -621,7 +641,8 @@
             return
         }
         if (
-            annotationButton?.visibility != View.VISIBLE &&
+            isAnnotationIntentResolvable &&
+                annotationButton?.visibility != View.VISIBLE &&
                 findInFileView?.visibility != View.VISIBLE
         ) {
             annotationButton?.post {
@@ -713,6 +734,10 @@
         pdfLoaderCallbacks?.selectionModel?.let {
             outState.putParcelable(KEY_PAGE_SELECTION, it.selection().get())
         }
+        outState.putBoolean(
+            KEY_ANNOTATION_BUTTON_VISIBILITY,
+            (annotationButton?.visibility == View.VISIBLE)
+        )
     }
 
     private fun loadFile(fileUri: Uri) {
@@ -743,7 +768,6 @@
             annotationButton?.visibility = View.GONE
         }
         localUri = fileUri
-        isDocumentLoadedFirstTime = true
     }
 
     private fun validateFileUri(fileUri: Uri) {
@@ -834,5 +858,6 @@
         private const val KEY_SHOW_ANNOTATION: String = "showEditFab"
         private const val KEY_PAGE_SELECTION: String = "currentPageSelection"
         private const val KEY_DOCUMENT_URI: String = "documentUri"
+        private const val KEY_ANNOTATION_BUTTON_VISIBILITY = "isAnnotationVisible"
     }
 }
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
index 5af4b14..0ab2bfe 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
@@ -66,6 +66,8 @@
 
     private PageViewFactory mPageViewFactory;
 
+    private boolean mIsConfigurationChanged = false;
+
     public PaginatedView(@NonNull Context context) {
         this(context, null);
     }
@@ -444,4 +446,12 @@
                     }
                 });
     }
+
+    public void setConfigurationChanged(boolean configurationChanged) {
+        this.mIsConfigurationChanged = configurationChanged;
+    }
+
+    public boolean isConfigurationChanged() {
+        return mIsConfigurationChanged;
+    }
 }
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
index 037f875..2fb40ab 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
@@ -80,7 +80,7 @@
             mIsPageScrollingUp = false;
         }
 
-        if (mIsAnnotationIntentResolvable) {
+        if (mIsAnnotationIntentResolvable && !mPaginatedView.isConfigurationChanged()) {
 
             if (!isAnnotationButtonVisible() && position.scrollY == 0
                     && mFindInFileView.getVisibility() == View.GONE) {
@@ -108,6 +108,9 @@
                     }
                 });
             }
+        } else if (mPaginatedView.isConfigurationChanged()
+                && position.scrollY != oldPosition.scrollY) {
+            mPaginatedView.setConfigurationChanged(false);
         }
 
         if (position.scrollY > 0) {
diff --git a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
index a7a90a4..0e5e70a 100644
--- a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
@@ -41,7 +41,8 @@
             android:textSize="20sp"
             android:clickable="true"
             android:focusable="true"
-            android:background="@null">
+            android:background="@null"
+            style="@style/TextAppearance.Material3.TitleMedium">
         </androidx.pdf.widget.SearchEditText>
 
         <TextView android:id="@+id/match_status_textview"
@@ -63,7 +64,8 @@
         android:cropToPadding="true"
         android:padding="3dp"
         android:scaleType="centerInside"
-        android:layout_margin="5dp"/>
+        android:layout_margin="5dp"
+        android:contentDescription = "@string/previous_button_description"/>
 
     <ImageButton
         android:id="@+id/find_next_btn"
@@ -76,7 +78,7 @@
         android:padding="3dp"
         android:scaleType="centerInside"
         android:layout_margin="5dp"
-        />
+        android:contentDescription = "@string/next_button_description"/>
     <ImageButton
         android:id="@+id/close_btn"
         android:layout_width="34dp"
@@ -89,6 +91,7 @@
         android:scaleType="centerInside"
         android:layout_marginVertical="5dp"
         android:layout_marginLeft="5dp"
-        android:layout_marginRight="10dp"/>
+        android:layout_marginRight="10dp"
+        android:contentDescription = "@string/close_button_description"/>
 
 </merge>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml b/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
index a62c083..78b5580 100644
--- a/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
@@ -30,4 +30,5 @@
     android:textColor="@color/google_grey"
     android:textSize="12sp"
     tools:ignore="RtlHardcoded"
-    tools:text="3 of 20" />
+    tools:text="3 of 20"
+    style="@style/TextAppearance.Material3.LabelMedium"/>
diff --git a/pdf/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index 2edf5ce..19b1121c 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Wysig lêer"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Voer wagwoord in om te ontsluit"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Probeer weer"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Kon nie die PDF-dokument verwerk nie!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Kon nie die lêer oopmaak nie. Moontlike toestemmingkwessie?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Bladsy is vir die PDF-dokument gebreek"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende data om die PDF-dokument te verwerk"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-am/strings.xml b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
index 90e2731..59e6e04 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ፋይል አርትዕ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ለመክፈት የይለፍ ቃል ያስገቡ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"እንደገና ሞክር"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"የPDF ሰነዱን ማሰናዳት አልተሳካም!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ፋይሉን መክፈት አልተሳካም። የፈቃድ ችግር ሊሆን ይችላል?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"ለPDF ሰነዱ ገፅ ተበላሽቷል"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ሰነዱን ለማሰናዳት በቂ ያልሆነ ውሂብ"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
index ee680cc..8f89292 100644
--- a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"تعديل الملف"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"يجب إدخال كلمة المرور لفتح القفل"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"إعادة المحاولة"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"‏تعذّرت معالجة مستند PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"تعذّر فتح الملف. هل توجد مشكلة محتملة في الأذونات؟"</string>
+    <string name="page_broken" msgid="2968770793669433462">"‏تعذّر تحميل صفحة من مستند PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"‏البيانات غير كافية لمعالجة مستند PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-as/strings.xml b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
index a4ed101..3de154e 100644
--- a/pdf/pdf-viewer/src/main/res/values-as/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ফাইল সম্পাদনা কৰক"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"আনলক কৰিবলৈ পাছৱৰ্ড দিয়ক"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"পুনৰ চেষ্টা কৰক"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF নথিখন প্ৰক্ৰিয়াকৰণ কৰিব পৰা নগ’ল!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ফাইলটো খুলিব পৰা নগ’ল। সম্ভাব্য অনুমতি সম্পৰ্কীয় সমস্যা?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF নথিৰ বাবে পৃষ্ঠাখন বিসংগতিপূৰ্ণ"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF নথিখন প্ৰক্ৰিয়াকৰণ কৰিবলৈ অপৰ্যাপ্ত ডেটা"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-az/strings.xml b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
index af09696..4293921 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Faylı redaktə edin"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Kiliddən çıxarmaq üçün parol daxil edin"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Yenə cəhd edin"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF sənədi emal edilmədi!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Fayl açılmadı. İcazə problemi var?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF sənədi üçün səhifədə xəta var"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF sənədini emal etmək üçün kifayət qədər data yoxdur"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
index 80ec514..2723618 100644
--- a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Izmeni fajl"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku za otključavanje"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Probaj ponovo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Obrada PDF dokumenta nije uspela."</string>
+    <string name="file_error" msgid="4003885928556884091">"Otvaranje fajla nije uspelo. Možda postoje problemi sa dozvolom?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Neispravna stranica za PDF dokument"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nedovoljno podataka za obradu PDF dokumenta"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-be/strings.xml b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
index 704938e..179a2e7 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Рэдагаваць файл"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Увядзіце пароль для разблакіроўкі"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Паўтарыць"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Не ўдалося апрацаваць дакумент PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"Не ўдалося адкрыць файл. Магчыма, праблема з дазволам?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Старонка дакумента PDF пашкоджана"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Не хапае даных для апрацоўкі дакумента PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
index 9970d18..67b6be7 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Редактиране на файла"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Въведете паролата, за да отключите"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Нов опит"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF документът не бе обработен."</string>
+    <string name="file_error" msgid="4003885928556884091">"Файлът не бе отворен. Възможно е да има проблем с разрешенията."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Невалидна страница в PDF документа"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Няма достатъчно данни за обработването на PDF документа"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
index db776a3..7b5287a 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ফাইল এডিট করুন"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"আনলক করতে পাসওয়ার্ড লিখুন"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"আবার চেষ্টা করুন"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"পিডিএফ ডকুমেন্ট প্রসেস করা যায়নি!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ফাইল খোলা যায়নি। অনুমতি সংক্রান্ত সমস্যার কারণে এটি হতে পারে?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"পিডিএফ ডকুমেন্টের ক্ষেত্রে পৃষ্ঠা ভেঙে গেছে"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"পিডিএফ ডকুমেন্ট প্রসেস করার জন্য যথেষ্ট ডেটা নেই"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
index 79afc95..4944bfa 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Uredite fajl"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku da otključate fajl"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Pokušajte ponovo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Obrada PDF dokumenta nije uspjela!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Otvaranje fajla nije uspjelo. Možda postoji problem s odobrenjem?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Stranica je prelomljena za PDF dokument"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
index 8eb179d..b8b2b4c 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edita el fitxer"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Introdueix la contrasenya per desbloquejar-lo"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Torna-ho a provar"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"No s\'ha pogut processar el document PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"No s\'ha pogut obrir el fitxer. És possible que hi hagi un problema de permisos?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"La pàgina no funciona per al document PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Les dades són insuficients per processar el document PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
index b7d47ba..395aa47 100644
--- a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Upravit soubor"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"K odemknutí zadejte heslo"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Zkusit znovu"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Dokument PDF se nepodařilo zpracovat"</string>
+    <string name="file_error" msgid="4003885928556884091">"Soubor se nepodařilo otevřít. Může se jednat o problém s oprávněním."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Dokument PDF obsahuje poškozenou stránku"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nedostatek dat ke zpracování dokumentu PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-da/strings.xml b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
index 94b020d..9b95de8 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Rediger fil"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Angiv adgangskode for at låse op"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Prøv igen"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF-dokumentet kunne ikke behandles"</string>
+    <string name="file_error" msgid="4003885928556884091">"Filen kunne ikke åbnes Mon der er et problem med tilladelserne?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Der er ikke nok data til at behandle PDF-dokumentet"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-de/strings.xml b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
index 4c2c0dd..1ac353e 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Datei bearbeiten"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Gib zum Entsperren ein Passwort ein"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Wiederholen"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF-Dokument konnte nicht verarbeitet werden."</string>
+    <string name="file_error" msgid="4003885928556884091">"Datei konnte nicht geöffnet werden. Möglicherweise ein Berechtigungsproblem?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Seite für PDF-Dokument ist fehlerhaft"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Keine ausreichenden Daten, um das PDF-Dokument zu verarbeiten"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-el/strings.xml b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
index 8f8883d..74b6dfa 100644
--- a/pdf/pdf-viewer/src/main/res/values-el/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Επεξεργασία αρχείου"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Εισαγάγετε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Επανάληψη"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Δεν ήταν δυνατή η επεξεργασία του εγγράφου PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Δεν ήταν δυνατό το άνοιγμα του αρχείου. Μήπως υπάρχει κάποιο πρόβλημα με την άδεια;"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Δεν ήταν δυνατή η φόρτωση του εγγράφου PDF από τη σελίδα"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Μη επαρκή δεδομένα για την επεξεργασία του εγγράφου PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
index f9ea156..609570b 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Retry"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Failed to process the PDF document!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
index f9ea156..609570b 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Retry"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Failed to process the PDF document!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
index f9ea156..609570b 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Retry"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Failed to process the PDF document!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
index 4057a540..dbe8fae 100644
--- a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar el archivo"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Ingresa la contraseña para desbloquear"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Reintentar"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"No se pudo procesar el documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"No se pudo abrir el archivo. ¿Puede que se deba a un problema de permisos?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"No hay datos suficientes para procesar el documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es/strings.xml b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
index 737c40ee..fa7f116 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar archivo"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Introduce la contraseña para desbloquear"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Reintentar"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"No se ha podido procesar el documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"No se ha podido abrir el archivo. ¿Puede que se deba a un problema de permisos?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Datos insuficientes para procesar el documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-et/strings.xml b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
index c6f4b12..4b085c6 100644
--- a/pdf/pdf-viewer/src/main/res/values-et/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Faili muutmine"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Avamiseks sisestage parool"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Proovi uuesti"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF-dokumendi töötlemine nurjus!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Faili avamine nurjus. Probleem võib olla seotud lubadega."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Rikutud leht PDF-dokumendis"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF-dokumendi töötlemiseks pole piisavalt andmeid"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
index 68a4806..458acf0 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editatu fitxategia"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Idatzi pasahitza desblokeatzeko"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Saiatu berriro"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Ezin izan da prozesatu PDF dokumentua!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Ezin izan da ireki fitxategia. Agian ez duzu horretarako baimenik?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF dokumentuaren orria hondatuta dago"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Ez dago behar adina daturik PDF dokumentua prozesatzeko"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
index 515f896..54bf7e7 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ویرایش فایل"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"گذرواژه را برای بازگشایی قفل وارد کنید"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"امتحان مجدد"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"‏سند PDF پردازش نشد!"</string>
+    <string name="file_error" msgid="4003885928556884091">"فایل باز نشد. احتمالاً مشکلی در اجازه وجود دارد؟"</string>
+    <string name="page_broken" msgid="2968770793669433462">"‏صفحه سند PDF خراب است"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"‏داده‌ها برای پردازش سند PDF کافی نیست"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
index 7a2acaa..0b03cc8e 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Muokkaa tiedostoa"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Poista lukitus lisäämällä salasana"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Yritä uudelleen"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF-dokumentin käsittely epäonnistui."</string>
+    <string name="file_error" msgid="4003885928556884091">"Tiedoston avaaminen epäonnistui. Mahdollinen lupaan liittyvä ongelma?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF-dokumenttiin liittyvä sivu on rikki"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Riittämätön data PDF-dokumentin käsittelyyn"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
index 94580dc..ffa53ba 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Entrez le mot de passe pour déverrouiller le fichier"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Réessayer"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Échec du traitement du document PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Échec de l\'ouverture du fichier. Problème d\'autorisation éventuel?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Page brisée pour le document PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
index 8c33af4..fd1ee39 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Saisissez le mot de passe pour procéder au déverrouillage"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Réessayer"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Échec du traitement du document PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"Échec de l\'ouverture du fichier. Problème d\'autorisation possible ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Page non fonctionnelle pour le document PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
index c5af498..5965cb5 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar o ficheiro"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Introduce o contrasinal para desbloquear o ficheiro"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Tentar de novo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Produciuse un erro ao procesar o documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Produciuse un erro ao abrir o ficheiro. É posible que haxa problemas co permiso?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Non funciona a páxina para o documento PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Os datos non son suficientes para procesar o documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
index 15ed454..e8bb6cf 100644
--- a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ફાઇલમાં ફેરફાર કરો"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"અનલૉક કરવા માટે પાસવર્ડ દાખલ કરો"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ફરી પ્રયાસ કરો"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF દસ્તાવેજ પર પ્રક્રિયા કરવામાં નિષ્ફળ રહ્યાં!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ફાઇલ ખોલવામાં નિષ્ફળ રહ્યાં. શું તમારી પાસે આની પરવાનગી નથી?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF દસ્તાવેજ માટે પેજ લોડ થઈ રહ્યું નથી"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF દસ્તાવેજ પર પ્રક્રિયા કરવા માટે પર્યાપ્ત ડેટા નથી"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
index e7d9248..5d44d0d 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"फ़ाइल में बदलाव करें"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करने के लिए पासवर्ड डालें"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"फिर कोशिश करें"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF दस्तावेज़ को प्रोसेस नहीं किया जा सका!"</string>
+    <string name="file_error" msgid="4003885928556884091">"फ़ाइल नहीं खोली जा सकी. क्या आपके पास इसकी अनुमति नहीं है?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF दस्तावेज़ के लिए पेज लोड नहीं हो रहा है"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तावेज़ को प्रोसेस करने के लिए, ज़रूरत के मुताबिक डेटा नहीं है"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
index bb240c5..a0c4be4 100644
--- a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Uređivanje datoteke"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Unesite zaporku za otključavanje"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Pokušaj ponovo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Obrada PDF dokumenta nije uspjela!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Otvaranje datoteke nije uspjelo. Možda postoji problem s dopuštenjem?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Stranica je raščlanjena za PDF dokument"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
index 74e1a51..41020ca 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Fájl szerkesztése"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"A feloldáshoz írja be a jelszót"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Újra"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Nem sikerült feldolgozni a PDF-dokumentumot!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Nem sikerült megnyitni a fájlt. Engedéllyel kapcsolatos problémáról lehet szó?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Az oldal nem tölt be a PDF-dokumentumban"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nem áll rendelkezésre elegendő adat a PDF-dokumentum feldolgozásához"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
index 52602c5..2e482a1 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Փոփոխել ֆայլը"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Մուտքագրեք գաղտնաբառը՝ ապակողպելու համար"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Նորից փորձել"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Չհաջողվեց մշակել PDF փաստաթուղթը"</string>
+    <string name="file_error" msgid="4003885928556884091">"Չհաջողվեց բացել ֆայլը։ Գուցե թույլտվության հետ կապված խնդի՞ր է։"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF փաստաթղթի էջը վնասված է"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Ոչ բավարար տվյալներ PDF փաստաթղթի մշակման համար"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-in/strings.xml b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
index 558c6f7..0e9397c 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Masukkan sandi untuk membuka kunci"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Coba lagi"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Gagal memproses dokumen PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Gagal membuka file. Kemungkinan masalah izin?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Halaman dokumen PDF rusak"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Data tidak cukup untuk memproses dokumen PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-is/strings.xml b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
index 4cd1318..1a2f9ff 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Breyta skrá"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Sláðu inn aðgangsorð til að opna"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Reyna aftur"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Ekki tókst að vinna úr PDF-skjalinu!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Ekki tókst að opna skrána. Hugsanlega vandamál tengt heimildum?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Síða í PDF-skjali er gölluð"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Ekki næg gögn fyrir úrvinnslu á PDF-skjali"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-it/strings.xml b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
index 113a272..3025646 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Modifica file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Inserisci la password per sbloccare il file"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Riprova"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Impossibile elaborare il documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Impossibile aprire il file. Possibile problema di autorizzazione?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Pagina inaccessibile per il documento PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Dati insufficienti per l\'elaborazione del documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
index 6edfee1..0d21739 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"עריכת הקובץ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"צריך להזין סיסמה לביטול הנעילה"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ניסיון חוזר"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"‏לא ניתן לעבד את מסמך ה-PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"לא ניתן לפתוח את הקובץ. יכול להיות שיש בעיה בהרשאה."</string>
+    <string name="page_broken" msgid="2968770793669433462">"‏קישור מנותק בדף למסמך ה-PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"‏אין מספיק נתונים כדי לעבד את מסמך ה-PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
index 52edede..c5db90d 100644
--- a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ファイルを編集"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ロックを解除するには、パスワードを入力してください"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"再試行"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ドキュメントを処理できませんでした"</string>
+    <string name="file_error" msgid="4003885928556884091">"ファイルを開けませんでした。権限に問題がある可能性はありませんか?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ドキュメントのページが壊れています"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"データ不足のため PDF ドキュメントを処理できません"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
index 71561bee9..cc30d31 100644
--- a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ფაილის რედაქტირება"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"პაროლის შეყვანა განბლოკვისთვის"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ხელახლა ცდა"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF დოკუმენტის დამუშავება ვერ მოხერხდა!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ფაილის გახსნა ვერ მოხერხდა. შესაძლოა ნებართვის პრობლემა იყოს?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF დოკუმენტის გვერდი დაზიანებულია"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"მონაცემები არ არის საკმარისი PDF დოკუმენტის დასამუშავებლად"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
index cf6b775..84b91ca 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Файлды өңдеу"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Құлыпты ашу үшін құпия сөзді енгізіңіз."</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Қайталау"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF құжаты өңделмеді."</string>
+    <string name="file_error" msgid="4003885928556884091">"Файл ашылмады. Бәлкім, рұқсатқа қатысты бір мәселе бар?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF құжатының беті бұзылған."</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF құжатын өңдеу үшін деректер жеткіліксіз."</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-km/strings.xml b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
index 89d0537..2e9bf3b 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"កែ​ឯកសារ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"បញ្ចូល​ពាក្យ​សម្ងាត់ ដើម្បី​ដោះ​សោ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ព្យាយាមម្ដងទៀត"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"មិនអាច​ដំណើរការ​ឯកសារ PDF បានទេ!"</string>
+    <string name="file_error" msgid="4003885928556884091">"មិនអាច​បើក​ឯកសារនេះ​បានទេ។ អាចមាន​បញ្ហា​នៃ​ការអនុញ្ញាតឬ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"ទំព័រ​មិនដំណើរការ​សម្រាប់​ឯកសារ PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"មានទិន្នន័យ​មិនគ្រប់គ្រាន់​សម្រាប់​ដំណើរការ​ឯកសារ PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
index 55d9479..00aa9bb 100644
--- a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ಫೈಲ್ ಎಡಿಟ್‌ ಮಾಡಿ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ಅನ್‌ಲಾಕ್‌ ಮಾಡಲು ಪಾಸವರ್ಡ್‌ ಅನ್ನು ನಮೂದಿಸಿ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ವಿಫಲವಾಗಿದೆ!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ಫೈಲ್ ತೆರೆಯಲು ವಿಫಲವಾಗಿದೆ. ಸಂಭವನೀಯ ಅನುಮತಿ ಸಮಸ್ಯೆ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ಡಾಕ್ಯುಮೆಂಟ್‌ಗೆ ಸಂಬಂಧಿಸಿದ ಪುಟ ಮುರಿದಿದೆ"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಸಾಕಷ್ಟು ಡೇಟಾ ಇಲ್ಲ"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
index 4a7d52a..8189d99 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"파일 수정"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"잠금 해제하려면 비밀번호 입력"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"재시도"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF 문서를 처리할 수 없습니다."</string>
+    <string name="file_error" msgid="4003885928556884091">"파일을 열 수 없습니다. 권한 문제가 있을 수 있나요?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF 문서의 페이지가 손상되었습니다."</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF 문서 처리를 위한 데이터가 부족합니다."</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
index 2eeed1e..a0e0558 100644
--- a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Файлды түзөтүү"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Кулпусун ачуу үчүн сырсөздү териңиз"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Кайталоо"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF документи иштетилген жок."</string>
+    <string name="file_error" msgid="4003885928556884091">"Файл ачылган жок. Керектүү уруксаттар жок окшойт."</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF документинин барагы бузук"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF документин иштетүү үчүн маалымат жетишсиз"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
index 4ced9ae..06a320b 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ແກ້ໄຂໄຟລ໌"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ໃສ່ລະຫັດເພື່ອປົດລັອກ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ລອງໃໝ່"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"ປະມວນຜົນເອກະສານ PDF ບໍ່ສຳເລັດ!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ເປີດໄຟລ໌ບໍ່ສຳເລັດ. ອາດເປັນຍ້ອນບັນຫາທາງການອະນຸຍາດບໍ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"ໜ້າເສຍຫາຍສໍາລັບເອກະສານ PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"ຂໍ້ມູນບໍ່ພຽງພໍສໍາລັບການປະມວນຜົນເອກະສານ PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
index 3fb3087..2d6a562 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Redaguoti failą"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Įveskite slaptažodį, kad atrakintumėte"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Bandyti dar kartą"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Nepavyko apdoroti PDF dokumento!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Nepavyko atidaryti failo. Galima su leidimais susijusi problema?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Sugadintas PDF dokumento puslapis"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nepakanka duomenų PDF dokumentui apdoroti"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
index b6420dd..84afd6d 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Rediģēt failu"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Lai atbloķētu, ievadiet paroli."</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Mēģināt vēlreiz"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Neizdevās apstrādāt PDF dokumentu."</string>
+    <string name="file_error" msgid="4003885928556884091">"Neizdevās atvērt failu. Iespējams, ir radusies problēma ar atļaujām."</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF dokumenta lapa ir bojāta"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nepietiekams datu apjoms, lai apstrādātu PDF dokumentu"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
index 4b004f5..854802a 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Изменете ја датотеката"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Внесете лозинка за да отклучите"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Обидете се пoвторно"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Не можеше да се обработи PDF-документот!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Не можеше да се отвори датотеката. Можеби има проблем со дозволата?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Страницата не може да го вчита PDF-документот"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Недоволно податоци за обработка на PDF-документот"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
index 082787a..516f0cc 100644
--- a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ഫയൽ എഡിറ്റ് ചെയ്യുക"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"അൺലോക്ക് ചെയ്യാൻ പാസ്‌വേഡ് നൽകുക"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"വീണ്ടും ശ്രമിക്കുക"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ഡോക്യുമെന്റ് പ്രോസസ് ചെയ്യാനായില്ല!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ഫയൽ തുറക്കാനായില്ല. അനുമതി സംബന്ധിച്ച പ്രശ്‌നമാകാൻ സാധ്യതയുണ്ടോ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ഡോക്യുമെന്റിനായി പേജ് ലോഡ് ചെയ്യാനായില്ല"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ഡോക്യുമെന്റ് പ്രോസസ് ചെയ്യാൻ മതിയായ ഡാറ്റയില്ല"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
index 2716373..65ed1dc 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Файлыг засах"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Түгжээг тайлахын тулд нууц үг оруулна уу"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Дахин оролдох"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF баримт бичгийг боловсруулж чадсангүй!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Файлыг нээж чадсангүй. Зөвшөөрөлтэй холбоотой асуудал байж болох уу?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF баримт бичгийн хуудас эвдэрсэн"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF баримт бичгийг боловсруулахад өгөгдөл хангалтгүй байна"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
index 3373919..ae238c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"फाइल संपादित करा"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करण्यासाठी पासवर्ड एंटर करा"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"पुन्हा प्रयत्न करा"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF दस्तऐवजावर प्रक्रिया करता आली नाही!"</string>
+    <string name="file_error" msgid="4003885928556884091">"फाइल उघडता आली नाही. परवानगीशी संबंधित संभाव्य समस्या?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"पीडीएफ दस्तऐवजासाठी पेज खंडित झाले आहे"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तऐवजावर प्रक्रिया करण्यासाठी डेटा पुरेसा नाही"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
index 40c88bb..b42f674 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edit fail"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Masukkan kata laluan untuk membuka kunci"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Cuba lagi"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Gagal memproses dokumen PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Gagal membuka fail. Kemungkinan terdapat masalah berkaitan dengan kebenaran?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Halaman rosak untuk dokumen PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Data tidak mencukupi untuk memproses dokumen PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-my/strings.xml b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
index 2515cd7..ecaf6e9 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ဖိုင် တည်းဖြတ်ရန်"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ဖွင့်ရန် စကားဝှက်ထည့်ပါ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ထပ်စမ်းရန်"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF မှတ်တမ်း လုပ်ဆောင်၍မရလိုက်ပါ။"</string>
+    <string name="file_error" msgid="4003885928556884091">"ဖိုင်ကို ဖွင့်၍မရလိုက်ပါ။ ခွင့်ပြုချက် ပြဿနာ ဖြစ်နိုင်လား။"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF မှတ်တမ်းအတွက် စာမျက်နှာ ပျက်နေသည်"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF မှတ်တမ်း လုပ်ဆောင်ရန်အတွက် ဒေတာ မလုံလောက်ပါ"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
index a6ec89d8..9bb458d 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Endre filen"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Skriv inn passordet for å låse opp"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Prøv på nytt"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Kunne ikke behandle PDF-dokumentet"</string>
+    <string name="file_error" msgid="4003885928556884091">"Kunne ikke åpne filen. Kan det være et problem med tillatelser?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Det er utilstrekkelige data for behandling av PDF-dokumentet"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
index feadb87..346ef60 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"फाइल सम्पादन गर्नुहोस्"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"अनलक गर्न पासवर्ड हाल्नुहोस्"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"फेरि प्रयास गर्नुहोस्"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF डकुमेन्ट प्रोसेस गर्न सकिएन!"</string>
+    <string name="file_error" msgid="4003885928556884091">"फाइल खोल्न सकिएन। तपाईंसँग यो फाइल खोल्ने अनुमति छैन?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF डकुमेन्टको पेज लोड गर्न सकिएन"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF डकुमेन्ट प्रोसेस गर्न पर्याप्त जानकारी छैन"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
index a08a61e..a8a14e9 100644
--- a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Bestand bewerken"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Voer het wachtwoord in om te ontgrendelen"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Opnieuw proberen"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Kan het pdf-document niet verwerken"</string>
+    <string name="file_error" msgid="4003885928556884091">"Kan het bestand niet openen. Mogelijk rechtenprobleem?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Pagina van het pdf-document kan niet worden geladen"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende gegevens om het pdf-document te verwerken"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-or/strings.xml b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
index d0ab8f9..26a54de 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ଫାଇଲକୁ ଏଡିଟ କରନ୍ତୁ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ଅନଲକ କରିବା ପାଇଁ ପାସୱାର୍ଡ ଲେଖନ୍ତୁ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ଡକ୍ୟୁମେଣ୍ଟକୁ ପ୍ରକ୍ୱିୟାନ୍ୱିତ କରିବାରେ ବିଫଳ ହୋଇଛି!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ଫାଇଲ ଖୋଲିବାରେ ବିଫଳ ହୋଇଛି। ସମ୍ଭାବ୍ୟ ଅନୁମତି ସମସ୍ୟା ଅଛି?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପାଇଁ ପୃଷ୍ଠା ବିଭାଜିତ ହୋଇଛି"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପ୍ରକ୍ରିୟାକରଣ ପାଇଁ ପର୍ଯ୍ୟାପ୍ତ ଡାଟା ନାହିଁ"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
index 429da57..876f5ae 100644
--- a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ਫ਼ਾਈਲ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਦਾਖਲ ਕਰੋ"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ਦਸਤਾਵੇਜ਼ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ਫ਼ਾਈਲ ਨੂੰ ਖੋਲ੍ਹਣਾ ਅਸਫਲ ਰਿਹਾ। ਕੀ ਸੰਭਵ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਸਮੱਸਿਆ ਹੈ?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ਦਸਤਾਵੇਜ਼ ਲਈ ਪੰਨਾ ਲੋਡ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ਦਸਤਾਵੇਜ਼ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਲਈ ਲੋੜੀਂਦਾ ਡਾਟਾ ਨਹੀਂ ਹੈ"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
index f9b9985..f791b6d 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Edytuj plik"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Podaj hasło, aby odblokować"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Spróbuj jeszcze raz"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Nie udało się przetworzyć dokumentu PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"Nie udało się otworzyć pliku. Może to przez problem z uprawnieniami?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Strona w dokumencie PDF jest uszkodzona"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Brak wystarczającej ilości danych do przetworzenia dokumentu PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
index 20f2827..7e0ddc0 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Tentar de novo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Falha no processamento do documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Falha ao abrir o arquivo. Possível problema de permissão?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
index b8af77d..53c34e4 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar ficheiro"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Introduza a palavra-passe para desbloquear"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Tentar novamente"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Falha ao processar o documento PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Falha ao abrir o ficheiro. Possível problema de autorização?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Página danificada para o documento PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processar o documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
index 20f2827..7e0ddc0 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Tentar de novo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Falha no processamento do documento PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Falha ao abrir o arquivo. Possível problema de permissão?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
index f0c516a..06344cf 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Editează fișierul"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Introdu parola pentru a debloca"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Reîncearcă"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Nu s-a putut procesa documentul PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Nu s-a putut deschide fișierul. Există vreo problemă cu permisiunile?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Pagină deteriorată pentru documentul PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Date insuficiente pentru procesarea documentului PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
index 9555bdf..7f1fe6b 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Редактировать файл"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Введите пароль для разблокировки."</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Повторить попытку"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Не удалось обработать документ PDF"</string>
+    <string name="file_error" msgid="4003885928556884091">"Не удалось открыть файл. Возможно, нет необходимых разрешений."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Страница документа PDF повреждена"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Недостаточно данных для обработки документа PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-si/strings.xml b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
index 664ae4d..54c42c3 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ගොනුව සංස්කරණ කරන්න"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"අගුලු හැරීමට මුරපදය ඇතුළත් කරන්න"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"යළි උත්සාහ කරන්න"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ලේඛනය සැකසීමට අසමත් විය!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ගොනුව විවෘත කිරීමට අසමත් විය. අවසර ගැටලුවක් විය හැකි ද?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ලේඛනය සඳහා පිටුව හානි වී ඇත"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ලේඛනය සැකසීම සඳහා ප්‍රමාණවත් දත්ත නොමැත"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
index 8368b60..f2886cd 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Upraviť súbor"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Zadajte heslo na odomknutie"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Skúsiť znova"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Dokument vo formáte PDF sa nepodarilo spracovať."</string>
+    <string name="file_error" msgid="4003885928556884091">"Súbor sa nepodarilo otvoriť. Možno sa vyskytol problém s povolením."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Stránka sa v dokumente vo formáte PDF nedá načítať"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"V dokumente vo formáte PDF nie je dostatok údajov na spracovanie"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
index 93a60e9..457b9f1 100644
--- a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Urejanje datoteke"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Vnesite geslo za odklepanje"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Poskusi znova"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Obdelava dokumenta PDF ni uspela."</string>
+    <string name="file_error" msgid="4003885928556884091">"Odpiranje datoteke ni uspelo. Ali morda gre za težavo z dovoljenjem?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Strani iz dokumenta PDF ni mogoče prikazati"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Nezadostni podatki za obdelavo dokumenta PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
index 2d3e4b8..ece0da4 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Modifiko skedarin"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Fut fjalëkalimin për ta shkyçur"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Riprovo"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Përpunimi i dokumentit PDF dështoi"</string>
+    <string name="file_error" msgid="4003885928556884091">"Hapja e skedarit dështoi. Problem i mundshëm me lejet?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Faqe e dëmtuar për dokumentin PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Të dhëna të pamjaftueshme për përpunimin e dokumentit PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
index e6681ce..e8b8b9c 100644
--- a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Измени фајл"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Унесите лозинку за откључавање"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Пробај поново"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Обрада PDF документа није успела."</string>
+    <string name="file_error" msgid="4003885928556884091">"Отварање фајла није успело. Можда постоје проблеми са дозволом?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Неисправна страница за PDF документ"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Недовољно података за обраду PDF документа"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
index 6ffdb93..90ee5c1 100644
--- a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Redigera fil"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Ange lösenord för att låsa upp"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Försök igen"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Det gick inte att bearbeta PDF-dokumentet"</string>
+    <string name="file_error" msgid="4003885928556884091">"Det gick inte att öppna filen. Detta kan bero på ett behörighetsproblem."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Det gick inte att läsa in en sida i PDF-dokumentet"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Otillräcklig data för att behandla PDF-dokumentet"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
index 0855606..2219b10 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Badilisha faili"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Weka nenosiri ili ufungue"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Jaribu tena"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Imeshindwa kuchakata hati ya PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Imeshindwa kufungua faili. Je, linaweza kuwa tatizo la ruhusa?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Ukurasa wa hati ya PDF una tatizo"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Hamna data ya kutosha kuchakata hati ya PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
index a2f7743..4c28feb 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ஃபைலைத் திருத்து"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"அன்லாக் செய்ய கடவுச்சொல்லை டைப் செய்யவும்"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"மீண்டும் முயலுங்கள்"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF ஆவணத்தைச் செயலாக்க முடியவில்லை!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ஃபைலைத் திறக்க முடியவில்லை. அனுமதி தொடர்பான சிக்கல் உள்ளதா?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF ஆவணத்தை ஏற்ற முடியவில்லை"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF ஆவணத்தைச் செயலாக்குவதற்குப் போதுமான தரவு இல்லை"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-te/strings.xml b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
index c8bc3c8..26c2954 100644
--- a/pdf/pdf-viewer/src/main/res/values-te/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"ఫైల్‌ను ఎడిట్ చేయండి"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"అన్‌లాక్ చేయడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"మళ్లీ ట్రై చేయండి"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF డాక్యుమెంట్‌ను ప్రాసెస్ చేయడం విఫలమైంది!"</string>
+    <string name="file_error" msgid="4003885928556884091">"ఫైల్‌ను తెరవడం విఫలమైంది. అనుమతికి సంబంధించిన సమస్య కావచ్చా?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF డాక్యుమెంట్‌కు సంబంధించి పేజీ బ్రేక్ అయింది"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF డాక్యుమెంట్‌ను ప్రాసెస్ చేయడానికి డేటా తగినంత లేదు"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-th/strings.xml b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
index 953a605..4565375 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"แก้ไขไฟล์"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"ป้อนรหัสผ่านเพื่อปลดล็อก"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"ลองอีกครั้ง"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"ประมวลผลเอกสาร PDF ไม่สำเร็จ"</string>
+    <string name="file_error" msgid="4003885928556884091">"เปิดไฟล์ไม่สำเร็จ อาจเกิดจากปัญหาด้านสิทธิ์"</string>
+    <string name="page_broken" msgid="2968770793669433462">"หน้าในเอกสาร PDF เสียหาย"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"ข้อมูลไม่เพียงพอสำหรับการประมวลผลเอกสาร PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
index 9f5ea33..6399462 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"I-edit ang file"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Ilagay ang password para i-unlock"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Subukan ulit"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Hindi naproseso ang PDF na dokumento!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Hindi nabuksan ang file. Baka may isyu sa pahintulot?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Sira ang page para sa PDF na dokumento"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Kulang ang data para maproseso ang PDF na dokumento"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
index 7de909c..c306300 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Dosyayı düzenle"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Kilidi açmak için şifreyi girin"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Tekrar dene"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF dokümanı işlenemedi."</string>
+    <string name="file_error" msgid="4003885928556884091">"Dosya açılamadı. İzin sorunundan kaynaklanıyor olabilir mi?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF dokümanının sayfası bozuk"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF dokümanını işleyecek kadar yeterli veri yok"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
index 68a67d1..8585b92 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Редагувати файл"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Введіть пароль, щоб розблокувати"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Повторити"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Не вдалось обробити документ PDF."</string>
+    <string name="file_error" msgid="4003885928556884091">"Не вдалося відкрити файл. Можливо, виникла проблема з дозволом."</string>
+    <string name="page_broken" msgid="2968770793669433462">"Сторінку документа PDF пошкоджено"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Недостатньо даних для обробки документа PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
index 9790e9b..10a6c0a 100644
--- a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"فائل میں ترمیم کریں"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"غیر مقفل کرنے کیلئے پاس ورڈ درج کریں"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"پھر کوشش کریں"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"‏‫PDF دستاویز پر کارروائی کرنے میں ناکام!"</string>
+    <string name="file_error" msgid="4003885928556884091">"فائل کھولنے میں ناکام۔ کیا یہ اجازت کا مسئلہ ہو سکتا ہے؟"</string>
+    <string name="page_broken" msgid="2968770793669433462">"‏‫PDF دستاویز کیلئے شکستہ صفحہ"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"‏‫PDF دستاویز پر کارروائی کرنے کیلئے ڈیٹا ناکافی ہے"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
index 16dc40c..c313d09 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Faylni tahrirlash"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Ochish uchun parolni kiriting"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Qayta urinish"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"PDF hujjat qayta ishlanmadi!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Fayl ochilmadi. Ruxsat bilan muammo bormi?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF hujjat sahifasi yaroqsiz"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"PDF hujjatni qayta ishlash uchun kerakli axborotlar yetarli emas"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index df50366..ea11021 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Chỉnh sửa tệp"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Nhập mật khẩu để mở khoá"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Thử lại"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Không xử lý được tài liệu PDF này!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Không mở được tệp này. Có thể là do vấn đề về quyền?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Tài liệu PDF này bị lỗi trang"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Không đủ dữ liệu để xử lý tài liệu PDF này"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index 5b6ae98..aea5179 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"编辑文件"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"请输入密码进行解锁"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"重试"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"无法处理 PDF 文档!"</string>
+    <string name="file_error" msgid="4003885928556884091">"无法打开文件。可能是由于权限问题导致?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF 文档的页面已损坏"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"数据不足,无法处理 PDF 文档"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
index 509c774..8cff251 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"重試"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"無法處理 PDF 文件!"</string>
+    <string name="file_error" msgid="4003885928556884091">"無法開啟檔案。可能有權限問題?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF 文件頁面已損毀"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"沒有足夠資料處理 PDF 文件"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
index 8339ae6..57010b7 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"重試"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"無法處理此 PDF 文件!"</string>
+    <string name="file_error" msgid="4003885928556884091">"無法開啟檔案。有可能是權限問題?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"PDF 文件的頁面損毀"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"資料不足,無法處理 PDF 文件"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
index 73a60ae..8d4f6b1 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -52,12 +52,8 @@
     <string name="action_edit" msgid="5882082700509010966">"Hlela ifayela"</string>
     <string name="password_not_entered" msgid="8875370870743585303">"Faka iphasiwedi ukuvula"</string>
     <string name="retry_button_text" msgid="3443862378337999137">"Zama futhi"</string>
-    <!-- no translation found for pdf_error (3287950599604474450) -->
-    <skip />
-    <!-- no translation found for file_error (4003885928556884091) -->
-    <skip />
-    <!-- no translation found for page_broken (2968770793669433462) -->
-    <skip />
-    <!-- no translation found for needs_more_data (3520133467908240802) -->
-    <skip />
+    <string name="pdf_error" msgid="3287950599604474450">"Yehlulekile ukucubungula idokhumenti ye-PDF!"</string>
+    <string name="file_error" msgid="4003885928556884091">"Yehlulekile ukuvula ifayela. Inkinga yemvume engaba khona?"</string>
+    <string name="page_broken" msgid="2968770793669433462">"Ikhasi eliphuliwe ledokhumenti ye-PDF"</string>
+    <string name="needs_more_data" msgid="3520133467908240802">"Idatha enganele yokucubungula idokhumenti ye-PDF"</string>
 </resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/strings.xml b/pdf/pdf-viewer/src/main/res/values/strings.xml
index 80fb57f..79cf9a2 100644
--- a/pdf/pdf-viewer/src/main/res/values/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values/strings.xml
@@ -117,6 +117,15 @@
     <!-- Hint text: Placeholder text shown in search box until the user enters query text. [CHAR LIMIT=20] -->
     <string name="hint_find">Find in file</string>
 
+    <!-- Content description for previous button in find in file menu -->
+    <string name="previous_button_description">Previous</string>
+
+    <!-- Content description for next button in find in file menu -->
+    <string name="next_button_description">Next</string>
+
+    <!-- Content description for close button in find in file menu -->
+    <string name="close_button_description">Close</string>
+
     <!-- Message for no matches found when searching for query text inside the file. [CHAR LIMIT=40] -->
     <string name="message_no_matches_found">No matches found.</string>
 
diff --git a/privacysandbox/tools/integration-tests/testapp/.gitignore b/privacysandbox/tools/integration-tests/testapp/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/privacysandbox/tools/integration-tests/testapp/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/privacysandbox/tools/integration-tests/testapp/build.gradle b/privacysandbox/tools/integration-tests/testapp/build.gradle
index d9a24ab..dfb37b9 100644
--- a/privacysandbox/tools/integration-tests/testapp/build.gradle
+++ b/privacysandbox/tools/integration-tests/testapp/build.gradle
@@ -27,6 +27,8 @@
         applicationId "androidx.privacysandbox.tools.integration.testapp"
         minSdk 33
         compileSdk 35
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
     experimentalProperties["android.privacySandboxSdk.apiGenerator"] =
             project.dependencies.create(project(":privacysandbox:tools:tools-apigenerator"))
@@ -52,4 +54,14 @@
     implementation(libs.material)
     implementation(libs.constraintLayout)
     implementation(libs.kotlinCoroutinesAndroid)
+
+    androidTestImplementation(project(":activity:activity"))
+    androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.kotlinCoroutinesTest)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.junit)
 }
diff --git a/privacysandbox/tools/integration-tests/testapp/src/androidTest/java/androidx/privacysandbox/tools/integration/testapp/MainActivityTest.kt b/privacysandbox/tools/integration-tests/testapp/src/androidTest/java/androidx/privacysandbox/tools/integration/testapp/MainActivityTest.kt
new file mode 100644
index 0000000..781a309
--- /dev/null
+++ b/privacysandbox/tools/integration-tests/testapp/src/androidTest/java/androidx/privacysandbox/tools/integration/testapp/MainActivityTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.tools.integration.testapp
+
+import androidx.privacysandbox.tools.integration.testsdk.MySdk
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.resume
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class MainActivityTest {
+
+    @get:Rule val scenarioRule = ActivityScenarioRule(MainActivity::class.java)
+
+    @Before fun setUp() = runBlocking { getActivity().loadSdk() }
+
+    @Test
+    fun loadSdk_works() = runTest {
+        val sdk = getActivity().sdk
+
+        assertThat(sdk).isNotNull()
+    }
+
+    @Test
+    fun doSum_works() = runTest {
+        val sum = getSdk().doSum(5, 6)
+
+        assertThat(sum).isEqualTo(11)
+    }
+
+    private suspend fun getActivity(): MainActivity = suspendCancellableCoroutine {
+        scenarioRule.scenario.onActivity { activity -> it.resume(activity) }
+    }
+
+    private suspend fun getSdk(): MySdk = getActivity().sdk!!
+}
diff --git a/privacysandbox/tools/integration-tests/testapp/src/main/java/androidx/privacysandbox/tools/integration/testapp/MainActivity.kt b/privacysandbox/tools/integration-tests/testapp/src/main/java/androidx/privacysandbox/tools/integration/testapp/MainActivity.kt
index 5a2e26e..a4c92ac 100644
--- a/privacysandbox/tools/integration-tests/testapp/src/main/java/androidx/privacysandbox/tools/integration/testapp/MainActivity.kt
+++ b/privacysandbox/tools/integration-tests/testapp/src/main/java/androidx/privacysandbox/tools/integration/testapp/MainActivity.kt
@@ -17,34 +17,33 @@
 package androidx.privacysandbox.tools.integration.testapp
 
 import android.os.Bundle
-import android.util.Log
 import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
 import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.tools.integration.testsdk.MySdk
 import androidx.privacysandbox.tools.integration.testsdk.MySdkFactory.wrapToMySdk
-import kotlinx.coroutines.launch
 
 class MainActivity : AppCompatActivity() {
-    private lateinit var sdk: MySdk
+    internal var sdk: MySdk? = null
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
-
-        lifecycleScope.launch {
-            sdk = loadSdk()
-            Log.e("Test App MainActivity", "Sum = ${sdk.doSum(42, 2)}")
-        }
     }
 
-    suspend fun loadSdk(): MySdk {
+    internal suspend fun loadSdk() {
+        if (this.sdk != null) return
+
         val sandboxManagerCompat = SdkSandboxManagerCompat.from(this)
         val sandboxedSdk =
-            sandboxManagerCompat.loadSdk(
-                "androidx.privacysandbox.tools.integration.sdk",
-                Bundle.EMPTY
-            )
-        return wrapToMySdk(sandboxedSdk.getInterface()!!)
+            try {
+                sandboxManagerCompat.loadSdk(
+                    "androidx.privacysandbox.tools.integration.sdk",
+                    Bundle.EMPTY
+                )
+            } catch (e: LoadSdkCompatException) {
+                sandboxManagerCompat.getSandboxedSdks().first()
+            }
+        sdk = sandboxedSdk.getInterface()?.let { wrapToMySdk(it) }
     }
 }
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
index e2faa3d..7c41a22 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
@@ -23,12 +23,14 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Paint
+import android.graphics.Path
 import android.net.Uri
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
 import android.provider.Settings
 import android.util.Log
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.webkit.WebResourceRequest
@@ -157,6 +159,46 @@
 
         private val viewColor = Color.rgb((0..255).random(), (0..255).random(), (0..255).random())
 
+        // Map that attaches each pointer to its path
+        private val pointerIdToPathMap = mutableMapOf<Int, Path>()
+
+        private val paint = Paint()
+
+        override fun onTouchEvent(event: MotionEvent): Boolean {
+            super.onTouchEvent(event)
+            when (event.actionMasked) {
+                // A new pointer is down, keep track of it using its id, and create
+                // new line (path) to draw
+                MotionEvent.ACTION_DOWN,
+                MotionEvent.ACTION_POINTER_DOWN -> {
+                    val pointerIdToAdd = event.getPointerId(event.actionIndex)
+                    val pathToAdd =
+                        Path().apply {
+                            moveTo(event.getX(event.actionIndex), event.getY(event.actionIndex))
+                        }
+                    pointerIdToPathMap[pointerIdToAdd] = pathToAdd
+                }
+
+                // Update paths as the pointers are moving
+                MotionEvent.ACTION_MOVE -> {
+                    for (i in 0 until event.pointerCount) {
+                        val path = pointerIdToPathMap[event.getPointerId(i)]
+                        path?.lineTo(event.getX(i), event.getY(i))
+                    }
+                }
+
+                // Stop drawing path of pointer that is now up
+                MotionEvent.ACTION_UP,
+                MotionEvent.ACTION_POINTER_UP -> {
+                    val pointerToRemove = event.getPointerId(event.actionIndex)
+                    pointerIdToPathMap.remove(pointerToRemove)
+                }
+                else -> return false
+            }
+            invalidate()
+            return true
+        }
+
         @SuppressLint("BanThreadSleep")
         override fun onDraw(canvas: Canvas) {
             // We are adding sleep to test the synchronization of the app and the sandbox view's
@@ -165,12 +207,18 @@
                 Thread.sleep(500)
             }
             super.onDraw(canvas)
-
-            val paint = Paint()
             paint.textSize = 50F
-
             canvas.drawColor(viewColor)
             canvas.drawText(text, 75F, 75F, paint)
+            pointerIdToPathMap.forEach { (_, path) -> canvas.drawPath(path, paint) }
+
+            setOnClickListener {
+                Log.i(TAG, "Click on ad detected")
+                val visitUrl = Intent(Intent.ACTION_VIEW)
+                visitUrl.data = Uri.parse(GOOGLE_URL)
+                visitUrl.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                context.startActivity(visitUrl)
+            }
         }
     }
 
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 2ac83da..5bec1b4 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -656,6 +656,7 @@
         )
     }
 
+    @Ignore // b/356742276
     @Test
     fun signalsOnlyCollectedWhenSignalOptionsNonEmpty() {
         addViewToLayoutAndWaitToBeActive()
diff --git a/profileinstaller/profileinstaller/api/1.4.0-beta01.txt b/profileinstaller/profileinstaller/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..a42473a
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/1.4.0-beta01.txt
@@ -0,0 +1,77 @@
+// Signature format: 4.0
+package androidx.profileinstaller {
+
+  public class ProfileInstallReceiver extends android.content.BroadcastReceiver {
+    ctor public ProfileInstallReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent?);
+    field public static final String ACTION_BENCHMARK_OPERATION = "androidx.profileinstaller.action.BENCHMARK_OPERATION";
+    field public static final String ACTION_INSTALL_PROFILE = "androidx.profileinstaller.action.INSTALL_PROFILE";
+    field public static final String ACTION_SAVE_PROFILE = "androidx.profileinstaller.action.SAVE_PROFILE";
+    field public static final String ACTION_SKIP_FILE = "androidx.profileinstaller.action.SKIP_FILE";
+  }
+
+  public class ProfileInstaller {
+    method @WorkerThread public static void writeProfile(android.content.Context);
+    method @WorkerThread public static void writeProfile(android.content.Context, java.util.concurrent.Executor, androidx.profileinstaller.ProfileInstaller.DiagnosticsCallback);
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_DOES_NOT_EXIST = 2; // 0x2
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_EXISTS = 1; // 0x1
+    field public static final int DIAGNOSTIC_PROFILE_IS_COMPRESSED = 5; // 0x5
+    field public static final int DIAGNOSTIC_REF_PROFILE_DOES_NOT_EXIST = 4; // 0x4
+    field public static final int DIAGNOSTIC_REF_PROFILE_EXISTS = 3; // 0x3
+    field public static final int RESULT_ALREADY_INSTALLED = 2; // 0x2
+    field public static final int RESULT_BASELINE_PROFILE_NOT_FOUND = 6; // 0x6
+    field public static final int RESULT_BENCHMARK_OPERATION_FAILURE = 15; // 0xf
+    field public static final int RESULT_BENCHMARK_OPERATION_SUCCESS = 14; // 0xe
+    field public static final int RESULT_BENCHMARK_OPERATION_UNKNOWN = 16; // 0x10
+    field public static final int RESULT_DELETE_SKIP_FILE_SUCCESS = 11; // 0xb
+    field public static final int RESULT_DESIRED_FORMAT_UNSUPPORTED = 5; // 0x5
+    field public static final int RESULT_INSTALL_SKIP_FILE_SUCCESS = 10; // 0xa
+    field public static final int RESULT_INSTALL_SUCCESS = 1; // 0x1
+    field public static final int RESULT_IO_EXCEPTION = 7; // 0x7
+    field public static final int RESULT_META_FILE_REQUIRED_BUT_NOT_FOUND = 9; // 0x9
+    field public static final int RESULT_NOT_WRITABLE = 4; // 0x4
+    field public static final int RESULT_PARSE_EXCEPTION = 8; // 0x8
+    field public static final int RESULT_SAVE_PROFILE_SIGNALLED = 12; // 0xc
+    field public static final int RESULT_SAVE_PROFILE_SKIPPED = 13; // 0xd
+    field public static final int RESULT_UNSUPPORTED_ART_VERSION = 3; // 0x3
+  }
+
+  public static interface ProfileInstaller.DiagnosticsCallback {
+    method public void onDiagnosticReceived(int, Object?);
+    method public void onResultReceived(int, Object?);
+  }
+
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result!> {
+    ctor public ProfileInstallerInitializer();
+    method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>!> dependencies();
+  }
+
+  public static class ProfileInstallerInitializer.Result {
+    ctor public ProfileInstallerInitializer.Result();
+  }
+
+  public final class ProfileVerifier {
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.profileinstaller.ProfileVerifier.CompilationStatus!> getCompilationStatusAsync();
+    method @WorkerThread public static androidx.profileinstaller.ProfileVerifier.CompilationStatus writeProfileVerification(android.content.Context);
+  }
+
+  public static class ProfileVerifier.CompilationStatus {
+    method public boolean appApkHasEmbeddedProfile();
+    method public int getProfileInstallResultCode();
+    method public boolean hasProfileEnqueuedForCompilation();
+    method public boolean isCompiledWithProfile();
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE = 1; // 0x1
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING = 3; // 0x3
+    field public static final int RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ = 131072; // 0x20000
+    field public static final int RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE = 196608; // 0x30000
+    field public static final int RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED = 327680; // 0x50000
+    field public static final int RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST = 65536; // 0x10000
+    field public static final int RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION = 262144; // 0x40000
+    field @Deprecated public static final int RESULT_CODE_NO_PROFILE = 0; // 0x0
+    field public static final int RESULT_CODE_NO_PROFILE_INSTALLED = 0; // 0x0
+    field public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2; // 0x2
+  }
+
+}
+
diff --git a/profileinstaller/profileinstaller/api/res-1.4.0-beta01.txt b/profileinstaller/profileinstaller/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/res-1.4.0-beta01.txt
diff --git a/profileinstaller/profileinstaller/api/restricted_1.4.0-beta01.txt b/profileinstaller/profileinstaller/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..a42473a
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,77 @@
+// Signature format: 4.0
+package androidx.profileinstaller {
+
+  public class ProfileInstallReceiver extends android.content.BroadcastReceiver {
+    ctor public ProfileInstallReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent?);
+    field public static final String ACTION_BENCHMARK_OPERATION = "androidx.profileinstaller.action.BENCHMARK_OPERATION";
+    field public static final String ACTION_INSTALL_PROFILE = "androidx.profileinstaller.action.INSTALL_PROFILE";
+    field public static final String ACTION_SAVE_PROFILE = "androidx.profileinstaller.action.SAVE_PROFILE";
+    field public static final String ACTION_SKIP_FILE = "androidx.profileinstaller.action.SKIP_FILE";
+  }
+
+  public class ProfileInstaller {
+    method @WorkerThread public static void writeProfile(android.content.Context);
+    method @WorkerThread public static void writeProfile(android.content.Context, java.util.concurrent.Executor, androidx.profileinstaller.ProfileInstaller.DiagnosticsCallback);
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_DOES_NOT_EXIST = 2; // 0x2
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_EXISTS = 1; // 0x1
+    field public static final int DIAGNOSTIC_PROFILE_IS_COMPRESSED = 5; // 0x5
+    field public static final int DIAGNOSTIC_REF_PROFILE_DOES_NOT_EXIST = 4; // 0x4
+    field public static final int DIAGNOSTIC_REF_PROFILE_EXISTS = 3; // 0x3
+    field public static final int RESULT_ALREADY_INSTALLED = 2; // 0x2
+    field public static final int RESULT_BASELINE_PROFILE_NOT_FOUND = 6; // 0x6
+    field public static final int RESULT_BENCHMARK_OPERATION_FAILURE = 15; // 0xf
+    field public static final int RESULT_BENCHMARK_OPERATION_SUCCESS = 14; // 0xe
+    field public static final int RESULT_BENCHMARK_OPERATION_UNKNOWN = 16; // 0x10
+    field public static final int RESULT_DELETE_SKIP_FILE_SUCCESS = 11; // 0xb
+    field public static final int RESULT_DESIRED_FORMAT_UNSUPPORTED = 5; // 0x5
+    field public static final int RESULT_INSTALL_SKIP_FILE_SUCCESS = 10; // 0xa
+    field public static final int RESULT_INSTALL_SUCCESS = 1; // 0x1
+    field public static final int RESULT_IO_EXCEPTION = 7; // 0x7
+    field public static final int RESULT_META_FILE_REQUIRED_BUT_NOT_FOUND = 9; // 0x9
+    field public static final int RESULT_NOT_WRITABLE = 4; // 0x4
+    field public static final int RESULT_PARSE_EXCEPTION = 8; // 0x8
+    field public static final int RESULT_SAVE_PROFILE_SIGNALLED = 12; // 0xc
+    field public static final int RESULT_SAVE_PROFILE_SKIPPED = 13; // 0xd
+    field public static final int RESULT_UNSUPPORTED_ART_VERSION = 3; // 0x3
+  }
+
+  public static interface ProfileInstaller.DiagnosticsCallback {
+    method public void onDiagnosticReceived(int, Object?);
+    method public void onResultReceived(int, Object?);
+  }
+
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result!> {
+    ctor public ProfileInstallerInitializer();
+    method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>!> dependencies();
+  }
+
+  public static class ProfileInstallerInitializer.Result {
+    ctor public ProfileInstallerInitializer.Result();
+  }
+
+  public final class ProfileVerifier {
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.profileinstaller.ProfileVerifier.CompilationStatus!> getCompilationStatusAsync();
+    method @WorkerThread public static androidx.profileinstaller.ProfileVerifier.CompilationStatus writeProfileVerification(android.content.Context);
+  }
+
+  public static class ProfileVerifier.CompilationStatus {
+    method public boolean appApkHasEmbeddedProfile();
+    method public int getProfileInstallResultCode();
+    method public boolean hasProfileEnqueuedForCompilation();
+    method public boolean isCompiledWithProfile();
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE = 1; // 0x1
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING = 3; // 0x3
+    field public static final int RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ = 131072; // 0x20000
+    field public static final int RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE = 196608; // 0x30000
+    field public static final int RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED = 327680; // 0x50000
+    field public static final int RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST = 65536; // 0x10000
+    field public static final int RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION = 262144; // 0x40000
+    field @Deprecated public static final int RESULT_CODE_NO_PROFILE = 0; // 0x0
+    field public static final int RESULT_CODE_NO_PROFILE_INSTALLED = 0; // 0x0
+    field public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2; // 0x2
+  }
+
+}
+
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
index ca3ada7..dddae7e 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
@@ -371,7 +371,7 @@
         }
     }
 
-    @FlakyTest(bugId = 261205680)
+    @Ignore // b/261205680
     @Test
     fun appendWithDelayedInvalidation() {
         val items = createItems(startId = 0, count = 90)
@@ -387,7 +387,7 @@
             // to the data source. it should not crash :)
             queryExecutor.filterFunction = {
                 // TODO(b/): Avoid relying on function name, very brittle.
-                !it.toString().contains("refreshInvalidationAsync")
+                !it.toString().contains("refreshInvalidation")
             }
 
             db.getDao().deleteItems(items.subList(0, 80).map { it.id })
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
index 1e9bc88..4e33f4f 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
@@ -166,4 +166,6 @@
 }
 
 expect object BaseAutoMigrationTest_AutoMigrationDatabaseConstructor :
-    RoomDatabaseConstructor<AutoMigrationDatabase>
+    RoomDatabaseConstructor<AutoMigrationDatabase> {
+    override fun initialize(): AutoMigrationDatabase
+}
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
index 321cc80..583772d 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
@@ -318,10 +318,10 @@
             )
         runProcessorTest(
             sources = listOf(src),
-            // TODO(kuanyingchou): Remove the "1.9" args when we move to KAPT4. Our processor
+            // TODO(b/314151707): Remove the "1.9" args when we move to KAPT4. Our processor
             //  doesn't get to run with KAPT3 and K2 as we pass "-Werror" and we got warning:
             //  "Kapt currently doesn't support language version 2.0+. Falling back to 1.9."
-            kotlincArguments = listOf("-Werror", "-language-version=1.9", "-api-version=1.9"),
+            kotlincArguments = listOf("-Werror") + KOTLINC_LANGUAGE_1_9_ARGS,
             javacArguments = listOf("-Werror") // needed for kapt as it uses javac,
         ) { invocation ->
             invocation.processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, "some warning")
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
index 5c79028..73f99d5 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -146,7 +146,7 @@
 
     private val functionByDescriptor: Map<String, KmFunctionContainer> by lazy {
         buildMap {
-            functionList.forEach { put(it.descriptor, it) }
+            functionList.forEach { function -> function.descriptor?.let { put(it, function) } }
             propertyList.forEach { property ->
                 property.getter?.descriptor?.let { put(it, property.getter) }
                 property.setter?.descriptor?.let { put(it, property.setter) }
@@ -221,7 +221,7 @@
     val name: String
     /** Name of the function in byte code */
     val jvmName: String
-    val descriptor: String
+    val descriptor: String?
     val typeParameters: List<KmTypeParameterContainer>
     val parameters: List<KmValueParameterContainer>
     val returnType: KmTypeContainer
@@ -255,8 +255,11 @@
     override val jvmName: String
         get() = kmFunction.signature!!.name
 
-    override val descriptor: String
-        get() = kmFunction.signature!!.toString()
+    override val descriptor: String?
+        get() {
+            // This could be null due to https://youtrack.jetbrains.com/issue/KT-70600
+            return kmFunction.signature?.toString()
+        }
 
     override val typeParameters: List<KmTypeParameterContainer>
         get() = kmFunction.typeParameters.map { it.asContainer() }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
index 3e57c9c..244cbf1 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
@@ -68,6 +68,7 @@
         runProcessorTest(
             sources = listOf(placeholder),
             classpath = dependency,
+            // https://github.com/google/ksp/issues/1865
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val kotlinSubject = invocation.processingEnv.requireTypeElement("foo.bar.KotlinSubject")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
index 0b80372..54daf29 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
@@ -142,6 +142,7 @@
         runKspTest(
             sources = listOf(source),
             classpath = classpath,
+            // https://github.com/google/ksp/issues/1640
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS,
             config = config,
         ) { invocation ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
index 59a484f..7c21438 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
@@ -22,6 +22,7 @@
 import androidx.room.compiler.processing.util.asJTypeName
 import androidx.room.compiler.processing.util.getMethodByJvmName
 import androidx.room.compiler.processing.util.getParameter
+import androidx.room.compiler.processing.util.runKaptTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 
@@ -46,4 +47,35 @@
             }
         }
     }
+
+    @Test
+    fun inlineReifiedFunctionAndKAPT4() {
+        val source =
+            Source.kotlin(
+                "Foo.kt",
+                """
+                class Foo {
+                    val f: String = "hi"
+                    inline fun <reified T> inlineReifiedFun(t: T) {}
+                }
+                """
+                    .trimIndent()
+            )
+        runKaptTest(sources = listOf(source), kotlincArguments = listOf("-Xuse-kapt4")) { invocation
+            ->
+            invocation.processingEnv.requireTypeElement("Foo").let { element ->
+                val f = element.getDeclaredFields().single()
+                // This shouldn't throw NullPointerException when inline reified functions are
+                // present.
+                f.getAllAnnotations().let {
+                    if (invocation.isKsp) {
+                        assertThat(it).isEmpty()
+                    } else {
+                        assertThat(it.count()).isEqualTo(1)
+                        assertThat(it.single().name).isEqualTo("NotNull")
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 6cb5d28..78b8497 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -172,6 +172,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1640
         overridesCheck(source, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS)
     }
 
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index 6b27602..2fa1cee 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -222,7 +222,7 @@
             """
                     .trimIndent()
             )
-        runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
+        runTest(listOf(mySource)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("Subject")
             element.getAnnotation(MainAnnotation::class)!!.let { annotation ->
                 assertThat(annotation.getAsTypeList("typeList").map { it.asTypeName() })
@@ -430,6 +430,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1963
         runTest(sources = listOf(kotlinSrc, javaSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
             invocation ->
             listOf("KotlinClass", "JavaClass")
@@ -642,6 +643,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1883
         runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
             invocation ->
             listOf("JavaSubject", "KotlinSubject")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index 27be10a..a07baba 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -168,6 +168,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1890
         runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
             invocation ->
             val typeElement = invocation.processingEnv.requireTypeElement("Foo")
@@ -475,7 +476,7 @@
             """
                     .trimIndent()
             )
-        runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
+        runTest(listOf(mySource)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("Subject")
             val annotation = element.requireAnnotation<MainAnnotation>()
 
@@ -947,6 +948,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1883
         runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
             invocation ->
             listOf("JavaSubject", "KotlinSubject")
@@ -1204,6 +1206,7 @@
                             .trimIndent()
                     )
                 ),
+            // https://github.com/google/ksp/issues/1882
             kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("test.Subject")
@@ -1337,6 +1340,7 @@
             )
 
         listOf(javaSource, kotlinSource).forEach { source ->
+            // https://github.com/google/ksp/issues/1882
             runTest(sources = listOf(source), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation
                 ->
                 fun XAnnotated.getAllAnnotationTypeElements(): List<XTypeElement> {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index fc1bb0a..a57519c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -1338,6 +1338,7 @@
                 """
                         .trimIndent()
                 ) as Source.KotlinSource,
+            // https://github.com/google/ksp/issues/1933
             kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val classJTypeName =
@@ -1459,8 +1460,7 @@
                         @MyAnnotation(stringParam = "2") MyInterface
                 """
                         .trimIndent()
-                ) as Source.KotlinSource,
-            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
+                ) as Source.KotlinSource
         ) { invocation ->
             val annotation = getAnnotation(invocation)
             // Compare the AnnotationSpec string ignoring whitespace
@@ -1583,8 +1583,7 @@
                 MyInterface
                 """
                         .trimIndent()
-                ) as Source.KotlinSource,
-            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
+                ) as Source.KotlinSource
         ) { invocation ->
             val aJTypeName = JClassName.get("test", "A")
             val aKTypeName = KClassName("test", "A")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
index 15781b1..ad73b88 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
@@ -868,6 +868,7 @@
                         .trimIndent()
                 )
             ),
+            // https://github.com/google/ksp/issues/1898
             kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation, precompiled ->
             val enclosingElement =
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 3166c55..c9d2cd7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -506,6 +506,7 @@
             """
                     .trimIndent()
             )
+        // https://github.com/google/ksp/issues/1640
         runProcessorTest(sources = listOf(src), kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS) {
             invocation ->
             val klass = invocation.processingEnv.requireTypeElement("MyDataClass")
@@ -1351,6 +1352,7 @@
         runProcessorTest(
             sources = sources,
             classpath = classpath,
+            // https://github.com/google/ksp/issues/1640
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             // we use this to remove the hash added by the compiler for function names that don't
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
index eef3af7..00c94ef 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -63,6 +63,7 @@
                             .trimIndent()
                     )
                 ),
+            // https://github.com/google/ksp/issues/1642
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             fun checkConstructor(className: String) {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
index 113d34ab..3b6de5d 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
@@ -195,6 +195,7 @@
                             .trimIndent()
                     )
                 ),
+            // Not yet implemented: KSValueArgumentLiteImpl.getLocation
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) {
             val fooElement = it.processingEnv.requireTypeElement("test.Foo")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 8544d232..7c7d413 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -1358,6 +1358,7 @@
                             .trimIndent()
                     )
                 ),
+            // https://github.com/google/ksp/issues/1890
             kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val appSubject = invocation.processingEnv.requireTypeElement("test.Subject")
@@ -1625,7 +1626,7 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
+        runTest(sources = listOf(src)) { invocation ->
             val defaultArgsConstructors =
                 invocation.processingEnv
                     .requireTypeElement("DefaultArgs")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 840c0e7..66738e0 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -1087,6 +1087,7 @@
             )
         runProcessorTest(
             sources = listOf(src, javaSource),
+            // https://github.com/google/ksp/issues/1918
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val styleApplier = invocation.processingEnv.requireType("StyleApplier")
@@ -1623,6 +1624,7 @@
                     )
                 ),
             createProcessingSteps = { listOf(WildcardProcessingStep()) },
+            // TODO(b/314151707): reproduce in the KSP project
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { result ->
             result.hasError()
@@ -2332,6 +2334,7 @@
                 } else {
                     emptyList()
                 },
+            // https://github.com/google/ksp/issues/1640
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
index 11f0abf..be50e44 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
@@ -564,6 +564,7 @@
     fun annotationValues() {
         runProcessorTest(
             sources = listOf(kotlinSrc, javaSrc),
+            // Not yet implemented: KSValueArgumentImpl.getParent
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
index f176019..51bdc2e 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -20,7 +20,6 @@
 import androidx.kruth.assertWithMessage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.javac.JavacProcessingEnv
-import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -819,7 +818,7 @@
             """
                     .trimIndent()
             )
-        simpleRun(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { env ->
+        simpleRun(sources = listOf(src)) { env ->
             val subject = env.requireTypeElement("Subject")
             subject.getDeclaredFields().forEach { assertThat(it.getter).isNotNull() }
             subject.getDeclaredMethods().forEach {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
index d6ea9e1..55ed9f6 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
@@ -52,6 +52,7 @@
     @Test
     fun descriptor_method_simple() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly("emptyMethod()V")
@@ -110,6 +111,7 @@
                 }
                 """
             ),
+            // https://github.com/google/ksp/issues/1952
             kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         )
         checkSources(
@@ -131,6 +133,7 @@
     @Test
     fun descriptor_method_erasured() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsAtLeast(
@@ -206,6 +209,7 @@
     @Test
     fun descriptor_class_erasured() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
@@ -279,6 +283,7 @@
     @Test
     fun descriptor_method_primitiveParams() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V")
@@ -317,6 +322,7 @@
     @Test
     fun descriptor_method_classParam_javaTypes() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
@@ -365,6 +371,7 @@
     @Test
     fun descriptor_method_classParam_testClass() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
@@ -410,6 +417,7 @@
     @Test
     fun descriptor_method_classParam_innerTestClass() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
@@ -469,6 +477,7 @@
     @Test
     fun descriptor_method_arrayParams() {
         fun checkSources(vararg sources: Source) {
+            // https://github.com/google/ksp/issues/1952
             runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
index 98645e6..ab1e12d 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
@@ -107,6 +107,7 @@
         runKspTest(
             sources = sources,
             classpath = classpath,
+            // https://github.com/google/ksp/issues/1930
             kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             collectSignaturesInto(invocation, ksp)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
index a07fca5..0f00fc6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
@@ -146,10 +146,10 @@
                     )
                     .write(context.processingEnv)
             }
-            if (db.constructorObjectElement != null) {
+            if (db.constructorObject != null) {
                 DatabaseObjectConstructorWriter(
                         database = db,
-                        constructorObjectElement = db.constructorObjectElement
+                        constructorObject = db.constructorObject
                     )
                     .write(context.processingEnv)
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index b49a6c2..6cf7930 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -34,6 +34,7 @@
 import androidx.room.vo.Dao
 import androidx.room.vo.DaoMethod
 import androidx.room.vo.Database
+import androidx.room.vo.DatabaseConstructor
 import androidx.room.vo.DatabaseView
 import androidx.room.vo.Entity
 import androidx.room.vo.FtsEntity
@@ -135,7 +136,7 @@
             errorMsg = ProcessorErrors.INVALID_DATABASE_VERSION
         )
 
-        val constructorObjectElement = processConstructorObject(element)
+        val constructorObject = processConstructorObject(element)
 
         val database =
             Database(
@@ -148,7 +149,7 @@
                 exportSchema = dbAnnotation.value.exportSchema,
                 enableForeignKeys = hasForeignKeys,
                 overrideClearAllTables = hasClearAllTables,
-                constructorObjectElement = constructorObjectElement
+                constructorObject = constructorObject
             )
         database.autoMigrations = processAutoMigrations(element, database.bundle)
         return database
@@ -543,7 +544,7 @@
         return result
     }
 
-    private fun processConstructorObject(element: XTypeElement): XTypeElement? {
+    private fun processConstructorObject(element: XTypeElement): DatabaseConstructor? {
         val annotation = element.getAnnotation(androidx.room.ConstructedBy::class)
         if (annotation == null) {
             // If no @ConstructedBy is present then validate target is JVM (including Android)
@@ -604,6 +605,16 @@
             return null
         }
 
-        return typeElement
+        val initializeExecutableElement =
+            context.processingEnv
+                .requireTypeElement(RoomTypeNames.ROOM_DB_CONSTRUCTOR)
+                .getDeclaredMethods()
+                .single()
+        val isInitOverridden =
+            typeElement.getDeclaredMethods().any {
+                it.overrides(initializeExecutableElement, typeElement)
+            }
+
+        return DatabaseConstructor(typeElement, isInitOverridden)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
index aaab5ca..15f95d4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -40,7 +40,7 @@
     val exportSchema: Boolean,
     val enableForeignKeys: Boolean,
     val overrideClearAllTables: Boolean,
-    val constructorObjectElement: XTypeElement?
+    val constructorObject: DatabaseConstructor?
 ) {
     // This variable will be set once auto-migrations are processed given the DatabaseBundle from
     // this object. This is necessary for tracking the versions involved in the auto-migration.
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt
similarity index 64%
copy from room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt
copy to room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt
index fb1781e..f3ae2ae 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/Placeholder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.room.paging
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.room.vo
+
+import androidx.room.compiler.processing.XTypeElement
+
+/** Represents the declared [androidx.room.ConstructedBy] referenced object. */
+data class DatabaseConstructor(val element: XTypeElement, val overridesInitialize: Boolean)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
index 09c9c47..7289096 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
@@ -18,10 +18,10 @@
 
 import androidx.room.compiler.codegen.toKotlinPoet
 import androidx.room.compiler.processing.XProcessingEnv
-import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.addOriginatingElement
 import androidx.room.ext.RoomTypeNames.ROOM_DB_CONSTRUCTOR
 import androidx.room.vo.Database
+import androidx.room.vo.DatabaseConstructor
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
@@ -30,19 +30,19 @@
 
 class DatabaseObjectConstructorWriter(
     private val database: Database,
-    private val constructorObjectElement: XTypeElement
+    private val constructorObject: DatabaseConstructor
 ) {
     fun write(processingEnv: XProcessingEnv) {
         val databaseClassName = database.typeName.toKotlinPoet()
-        val objectClassName = constructorObjectElement.asClassName().toKotlinPoet()
+        val objectClassName = constructorObject.element.asClassName().toKotlinPoet()
         val typeSpec =
             TypeSpec.objectBuilder(objectClassName)
                 .apply {
                     addOriginatingElement(database.element)
                     addModifiers(KModifier.ACTUAL)
-                    if (constructorObjectElement.isInternal()) {
+                    if (constructorObject.element.isInternal()) {
                         addModifiers(KModifier.INTERNAL)
-                    } else if (constructorObjectElement.isPublic()) {
+                    } else if (constructorObject.element.isPublic()) {
                         addModifiers(KModifier.PUBLIC)
                     }
                     addSuperinterface(
@@ -50,9 +50,14 @@
                     )
                     addFunction(
                         FunSpec.builder("initialize")
-                            .addModifiers(KModifier.OVERRIDE)
-                            .returns(databaseClassName)
-                            .addStatement("return %L()", database.implTypeName.toKotlinPoet())
+                            .apply {
+                                if (constructorObject.overridesInitialize) {
+                                    addModifiers(KModifier.ACTUAL)
+                                }
+                                addModifiers(KModifier.OVERRIDE)
+                                returns(databaseClassName)
+                                addStatement("return %L()", database.implTypeName.toKotlinPoet())
+                            }
                             .build()
                     )
                 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
index ecb2da5..76f8fb9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.processing.XProcessingEnvConfig
 import androidx.room.compiler.processing.XProcessingStep
 import androidx.room.compiler.processing.util.CompilationResultSubject
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runKspTest
@@ -40,7 +41,7 @@
         classpath = classpath,
         options = options,
         javacArguments = javacArguments,
-        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
         handler = handler
     )
 }
@@ -59,7 +60,7 @@
         classpath = classpath,
         options = options,
         javacArguments = javacArguments,
-        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
         createProcessingSteps = createProcessingSteps,
         onCompilationResult = onCompilationResult
     )
@@ -80,7 +81,7 @@
         classpath = classpath,
         options = options,
         javacArguments = javacArguments,
-        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
         javacProcessors = javacProcessors,
         symbolProcessorProviders = symbolProcessorProviders,
         onCompilationResult = onCompilationResult
@@ -102,8 +103,7 @@
             classpath = classpath,
             options = options,
             javacArguments = javacArguments,
-            kotlincArguments =
-                listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
             config = config,
             handler = handler
         )
@@ -113,8 +113,7 @@
             classpath = classpath,
             options = options,
             javacArguments = javacArguments,
-            kotlincArguments =
-                listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
             handler = handler
         )
     }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
index 0cc8204..421fb25 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
@@ -404,7 +404,7 @@
             exportSchema = false,
             enableForeignKeys = false,
             overrideClearAllTables = true,
-            constructorObjectElement = null,
+            constructorObject = null,
         )
     }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
index 46f81e2..536bc08 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
@@ -72,7 +72,7 @@
                 exportSchema = false,
                 enableForeignKeys = false,
                 overrideClearAllTables = true,
-                constructorObjectElement = null,
+                constructorObject = null,
             )
 
         val expectedLegacyHash =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
index bd893d3..3a6c7f7 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
@@ -93,6 +93,26 @@
         )
     }
 
+    @Test
+    fun actualDatabaseConstructor_overridesInitialize() {
+        val ctorSrc =
+            Source.kotlin(
+                "MyDatabaseCtor.kt",
+                """
+            import androidx.room.*
+
+            expect object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase> {
+                override fun initialize(): MyDatabase
+            }
+            """
+                    .trimIndent()
+            )
+        runTest(
+            sources = listOf(databaseSrc, ctorSrc),
+            expectedFilePath = getTestGoldenPath(testName.methodName)
+        )
+    }
+
     private fun getTestGoldenPath(testName: String): String {
         return "kotlinCodeGen/$testName.kt"
     }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt
new file mode 100644
index 0000000..599f927d
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt
@@ -0,0 +1,5 @@
+import androidx.room.RoomDatabaseConstructor
+
+public actual object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase> {
+    actual override fun initialize(): MyDatabase = MyDatabase_Impl()
+}
\ No newline at end of file
diff --git a/room/room-paging/bcv/native/current.txt b/room/room-paging/bcv/native/current.txt
index 5d6f9ec9..0fc19cc 100644
--- a/room/room-paging/bcv/native/current.txt
+++ b/room/room-paging/bcv/native/current.txt
@@ -6,3 +6,28 @@
 // - Show declarations: true
 
 // Library unique name: <androidx.room:room-paging>
+abstract class <#A: kotlin/Any> androidx.room.paging/LimitOffsetPagingSource : androidx.paging/PagingSource<kotlin/Int, #A> { // androidx.room.paging/LimitOffsetPagingSource|null[0]
+    constructor <init>(androidx.room/RoomRawQuery, androidx.room/RoomDatabase, kotlin/Array<out kotlin/String>...) // androidx.room.paging/LimitOffsetPagingSource.<init>|<init>(androidx.room.RoomRawQuery;androidx.room.RoomDatabase;kotlin.Array<out|kotlin.String>...){}[0]
+
+    final val db // androidx.room.paging/LimitOffsetPagingSource.db|{}db[0]
+        final fun <get-db>(): androidx.room/RoomDatabase // androidx.room.paging/LimitOffsetPagingSource.db.<get-db>|<get-db>(){}[0]
+    final val itemCount // androidx.room.paging/LimitOffsetPagingSource.itemCount|{}itemCount[0]
+        final fun <get-itemCount>(): kotlin/Int // androidx.room.paging/LimitOffsetPagingSource.itemCount.<get-itemCount>|<get-itemCount>(){}[0]
+    final val sourceQuery // androidx.room.paging/LimitOffsetPagingSource.sourceQuery|{}sourceQuery[0]
+        final fun <get-sourceQuery>(): androidx.room/RoomRawQuery // androidx.room.paging/LimitOffsetPagingSource.sourceQuery.<get-sourceQuery>|<get-sourceQuery>(){}[0]
+    open val jumpingSupported // androidx.room.paging/LimitOffsetPagingSource.jumpingSupported|{}jumpingSupported[0]
+        open fun <get-jumpingSupported>(): kotlin/Boolean // androidx.room.paging/LimitOffsetPagingSource.jumpingSupported.<get-jumpingSupported>|<get-jumpingSupported>(){}[0]
+
+    open fun convertRows(androidx.sqlite/SQLiteStatement, kotlin/Int): kotlin.collections/List<#A> // androidx.room.paging/LimitOffsetPagingSource.convertRows|convertRows(androidx.sqlite.SQLiteStatement;kotlin.Int){}[0]
+    open fun getRefreshKey(androidx.paging/PagingState<kotlin/Int, #A>): kotlin/Int? // androidx.room.paging/LimitOffsetPagingSource.getRefreshKey|getRefreshKey(androidx.paging.PagingState<kotlin.Int,1:0>){}[0]
+    open suspend fun load(androidx.paging/PagingSource.LoadParams<kotlin/Int>): androidx.paging/PagingSource.LoadResult<kotlin/Int, #A> // androidx.room.paging/LimitOffsetPagingSource.load|load(androidx.paging.PagingSource.LoadParams<kotlin.Int>){}[0]
+}
+
+final const val androidx.room.paging.util/INITIAL_ITEM_COUNT // androidx.room.paging.util/INITIAL_ITEM_COUNT|{}INITIAL_ITEM_COUNT[0]
+    final fun <get-INITIAL_ITEM_COUNT>(): kotlin/Int // androidx.room.paging.util/INITIAL_ITEM_COUNT.<get-INITIAL_ITEM_COUNT>|<get-INITIAL_ITEM_COUNT>(){}[0]
+
+final fun <#A: kotlin/Any> (androidx.paging/PagingState<kotlin/Int, #A>).androidx.room.paging.util/getClippedRefreshKey(): kotlin/Int? // androidx.room.paging.util/getClippedRefreshKey|[email protected]<kotlin.Int,0:0>(){0§<kotlin.Any>}[0]
+final fun androidx.room.paging.util/getLimit(androidx.paging/PagingSource.LoadParams<kotlin/Int>, kotlin/Int): kotlin/Int // androidx.room.paging.util/getLimit|getLimit(androidx.paging.PagingSource.LoadParams<kotlin.Int>;kotlin.Int){}[0]
+final fun androidx.room.paging.util/getOffset(androidx.paging/PagingSource.LoadParams<kotlin/Int>, kotlin/Int, kotlin/Int): kotlin/Int // androidx.room.paging.util/getOffset|getOffset(androidx.paging.PagingSource.LoadParams<kotlin.Int>;kotlin.Int;kotlin.Int){}[0]
+final suspend fun <#A: kotlin/Any> androidx.room.paging.util/queryDatabase(androidx.paging/PagingSource.LoadParams<kotlin/Int>, androidx.room/RoomRawQuery, androidx.room/RoomDatabase, kotlin/Int, kotlin/Function2<androidx.sqlite/SQLiteStatement, kotlin/Int, kotlin.collections/List<#A>>): androidx.paging/PagingSource.LoadResult<kotlin/Int, #A> // androidx.room.paging.util/queryDatabase|queryDatabase(androidx.paging.PagingSource.LoadParams<kotlin.Int>;androidx.room.RoomRawQuery;androidx.room.RoomDatabase;kotlin.Int;kotlin.Function2<androidx.sqlite.SQLiteStatement,kotlin.Int,kotlin.collections.List<0:0>>){0§<kotlin.Any>}[0]
+final suspend fun androidx.room.paging.util/queryItemCount(androidx.room/RoomRawQuery, androidx.room/RoomDatabase): kotlin/Int // androidx.room.paging.util/queryItemCount|queryItemCount(androidx.room.RoomRawQuery;androidx.room.RoomDatabase){}[0]
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index d457b62..8770d1d 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -16,6 +16,8 @@
 
 import androidx.build.PlatformIdentifier
 import androidx.build.LibraryType
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
@@ -38,6 +40,7 @@
                 api(libs.kotlinStdlib)
                 api("androidx.paging:paging-common:3.3.2")
                 api(project(":room:room-runtime"))
+                implementation(libs.atomicFu)
             }
         }
 
@@ -52,6 +55,18 @@
             dependsOn(commonMain)
         }
 
+        jvmNativeMain {
+            dependsOn(commonMain)
+        }
+
+        jvmMain {
+            dependsOn(jvmNativeMain)
+        }
+
+        nativeMain {
+            dependsOn(jvmNativeMain)
+        }
+
         androidInstrumentedTest {
             dependsOn(commonTest)
             dependencies {
@@ -65,6 +80,13 @@
                 implementation("androidx.paging:paging-testing:3.3.2")
             }
         }
+        targets.configureEach { target ->
+            if (target.platformType == KotlinPlatformType.native) {
+                target.compilations["main"].defaultSourceSet {
+                    dependsOn(nativeMain)
+                }
+            }
+        }
     }
 }
 
@@ -82,4 +104,5 @@
     inceptionYear = "2021"
     description = "Room Paging integration"
     legacyDisableKotlinStrictApiMode = true
+    metalavaK2UastEnabled = false // Due to b/360195094
 }
diff --git a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
index e4ccfc8..e2ff237 100644
--- a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
+++ b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
@@ -22,11 +22,14 @@
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadResult
+import androidx.paging.PagingState
 import androidx.paging.testing.TestPager
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
+import androidx.room.RoomRawQuery
+import androidx.room.paging.util.getClippedRefreshKey
 import androidx.room.util.getColumnIndexOrThrow
+import androidx.sqlite.SQLiteStatement
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -125,7 +128,7 @@
         dao.addAllItems(ITEMS_LIST)
         // count query is executed on first load
         pager.refresh()
-        assertThat(pagingSource.itemCount.get()).isEqualTo(100)
+        assertThat(pagingSource.itemCount).isEqualTo(100)
     }
 
     @Test
@@ -140,7 +143,7 @@
             // count query is executed on first load
             pager.refresh()
             // should be 60 instead of 100
-            assertThat(pagingSource.itemCount.get()).isEqualTo(60)
+            assertThat(pagingSource.itemCount).isEqualTo(60)
         }
 
     @Test
@@ -243,7 +246,7 @@
             // initial loadSize = 15, but limited by id < 50, should only load items 40 - 50
             assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(40, 50))
             // should have 50 items fulfilling condition of id < 50 (TestItem id 0 - 49)
-            assertThat(pagingSource.itemCount.get()).isEqualTo(50)
+            assertThat(pagingSource.itemCount).isEqualTo(50)
         }
 
     @Test
@@ -262,7 +265,7 @@
             val result = pager.refresh() as LoadResult.Page
 
             assertThat(result.data).containsExactly(ITEMS_LIST[90])
-            assertThat(pagingSource.itemCount.get()).isEqualTo(1)
+            assertThat(pagingSource.itemCount).isEqualTo(1)
         }
 
     @Test
@@ -281,7 +284,7 @@
             assertThat(result.data).isEmpty()
 
             // check that no append/prepend can be triggered
-            assertThat(pagingSource.itemCount.get()).isEqualTo(0)
+            assertThat(pagingSource.itemCount).isEqualTo(0)
             assertThat(result.nextKey).isNull()
             assertThat(result.prevKey).isNull()
             assertThat(result.itemsBefore).isEqualTo(0)
@@ -302,7 +305,7 @@
             // ensure that it respects SQLite's default behavior for negative LIMIT
             assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(0, 15))
             // should behave as if no LIMIT were set
-            assertThat(pagingSource.itemCount.get()).isEqualTo(100)
+            assertThat(pagingSource.itemCount).isEqualTo(100)
             assertThat(result.nextKey).isEqualTo(15)
             assertThat(result.prevKey).isNull()
             assertThat(result.itemsBefore).isEqualTo(0)
@@ -554,7 +557,7 @@
 
         // database should only have 40 items left. Refresh key is invalid at this point
         // (greater than item count after deletion)
-        assertThat(pagingSource2.itemCount.get()).isEqualTo(40)
+        assertThat(pagingSource2.itemCount).isEqualTo(40)
         // ensure that paging source can handle invalid refresh key properly
         // should load last page with items 25 - 40
         assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(25, 40))
@@ -584,7 +587,7 @@
         runPagingSourceTest { pager, pagingSource ->
             dao.addAllItems(ITEMS_LIST)
             pager.refresh()
-            assertThat(pagingSource.itemCount.get()).isEqualTo(100)
+            assertThat(pagingSource.itemCount).isEqualTo(100)
             // items id 0 - 29 deleted (30 items removed)
             dao.deleteTestItems(0, 29)
 
@@ -595,7 +598,7 @@
             val result = pager2.refresh(initialKey = 0) as LoadResult.Page
 
             // database should only have 70 items left
-            assertThat(pagingSource2.itemCount.get()).isEqualTo(70)
+            assertThat(pagingSource2.itemCount).isEqualTo(70)
             // first 30 items deleted, refresh should load starting from pos 31 (item id 30 - 45)
             assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(30, 45))
 
@@ -613,7 +616,7 @@
         runPagingSourceTest { pager, pagingSource ->
             dao.addAllItems(ITEMS_LIST)
             pager.refresh(initialKey = 30)
-            assertThat(pagingSource.itemCount.get()).isEqualTo(100)
+            assertThat(pagingSource.itemCount).isEqualTo(100)
             // items id 0 - 94 deleted (95 items removed)
             dao.deleteTestItems(0, 94)
 
@@ -624,7 +627,7 @@
             val result = pager2.refresh(initialKey = 0) as LoadResult.Page
 
             // database should only have 5 items left
-            assertThat(pagingSource2.itemCount.get()).isEqualTo(5)
+            assertThat(pagingSource2.itemCount).isEqualTo(5)
             // only 5 items should be loaded with offset = 0
             assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(95, 100))
 
@@ -653,7 +656,6 @@
     }
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class LimitOffsetPagingSourceTestWithFilteringExecutor {
@@ -759,11 +761,21 @@
     queryString: String = "SELECT * FROM $tableName ORDER BY id ASC",
 ) :
     LimitOffsetPagingSource<TestItem>(
-        sourceQuery = RoomSQLiteQuery.acquire(queryString, 0),
+        sourceQuery = RoomRawQuery(sql = queryString),
         db = db,
-        tables = arrayOf("$tableName")
+        tables = arrayOf(tableName)
     ) {
 
+    override fun convertRows(statement: SQLiteStatement, itemCount: Int): List<TestItem> {
+        val stmtIndexOfId = getColumnIndexOrThrow(statement, "id")
+        val data = mutableListOf<TestItem>()
+        while (statement.step()) {
+            val tmpId = statement.getInt(stmtIndexOfId)
+            data.add(TestItem(tmpId))
+        }
+        return data
+    }
+
     override fun convertRows(cursor: Cursor): List<TestItem> {
         val cursorIndexOfId = getColumnIndexOrThrow(cursor, "id")
         val data = mutableListOf<TestItem>()
@@ -773,6 +785,13 @@
         }
         return data
     }
+
+    override val jumpingSupported: Boolean
+        get() = true
+
+    override fun getRefreshKey(state: PagingState<Int, TestItem>): Int? {
+        return state.getClippedRefreshKey()
+    }
 }
 
 private val CONFIG = PagingConfig(pageSize = 5, enablePlaceholders = true, initialLoadSize = 15)
diff --git a/room/room-paging/src/main/AndroidManifest.xml b/room/room-paging/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from room/room-paging/src/main/AndroidManifest.xml
rename to room/room-paging/src/androidMain/AndroidManifest.xml
diff --git a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt
new file mode 100644
index 0000000..d9389a0
--- /dev/null
+++ b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.paging
+
+import android.database.AbstractCursor
+import androidx.sqlite.SQLiteStatement
+
+/** Wrapper class for backwards compatibility in room-paging. */
+internal class SQLiteStatementCursor(
+    private val statement: SQLiteStatement,
+    private val rowCount: Int
+) : AbstractCursor() {
+    override fun getCount(): Int = rowCount
+
+    override fun getColumnNames(): Array<String> = statement.getColumnNames().toTypedArray()
+
+    override fun getString(column: Int): String = statement.getText(column)
+
+    override fun getShort(column: Int): Short = statement.getLong(column).toShort()
+
+    override fun getInt(column: Int): Int = statement.getInt(column)
+
+    override fun getLong(column: Int): Long = statement.getLong(column)
+
+    override fun getFloat(column: Int): Float = statement.getFloat(column)
+
+    override fun getDouble(column: Int): Double = statement.getDouble(column)
+
+    override fun isNull(column: Int): Boolean = statement.isNull(column)
+
+    override fun onMove(oldPosition: Int, newPosition: Int): Boolean {
+        check(oldPosition + 1 == newPosition) {
+            "Compat cursor can only move forward one position at a time."
+        }
+        return statement.step()
+    }
+}
diff --git a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
index 316fe34..fb7d965 100644
--- a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
+++ b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
@@ -17,22 +17,16 @@
 package androidx.room.paging
 
 import android.database.Cursor
-import androidx.annotation.NonNull
 import androidx.annotation.RestrictTo
 import androidx.paging.PagingSource
 import androidx.paging.PagingState
 import androidx.room.RoomDatabase
+import androidx.room.RoomRawQuery
 import androidx.room.RoomSQLiteQuery
-import androidx.room.paging.util.INITIAL_ITEM_COUNT
-import androidx.room.paging.util.INVALID
-import androidx.room.paging.util.ThreadSafeInvalidationObserver
+import androidx.room.paging.CommonLimitOffsetImpl.Companion.BUG_LINK
 import androidx.room.paging.util.getClippedRefreshKey
-import androidx.room.paging.util.queryDatabase
-import androidx.room.paging.util.queryItemCount
-import androidx.room.withTransaction
+import androidx.sqlite.SQLiteStatement
 import androidx.sqlite.db.SupportSQLiteQuery
-import java.util.concurrent.atomic.AtomicInteger
-import kotlinx.coroutines.withContext
 
 /**
  * An implementation of [PagingSource] to perform a LIMIT OFFSET query
@@ -42,11 +36,22 @@
  * when data changes.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class LimitOffsetPagingSource<Value : Any>(
-    private val sourceQuery: RoomSQLiteQuery,
-    private val db: RoomDatabase,
+actual abstract class LimitOffsetPagingSource<Value : Any>
+actual constructor(
+    actual val sourceQuery: RoomRawQuery,
+    actual val db: RoomDatabase,
     vararg tables: String,
 ) : PagingSource<Int, Value>() {
+    constructor(
+        sourceQuery: RoomSQLiteQuery,
+        db: RoomDatabase,
+        vararg tables: String,
+    ) : this(
+        sourceQuery =
+            RoomRawQuery(sql = sourceQuery.sql, onBindStatement = { sourceQuery.bindTo(it) }),
+        db = db,
+        tables = tables
+    )
 
     constructor(
         supportSQLiteQuery: SupportSQLiteQuery,
@@ -58,77 +63,27 @@
         tables = tables,
     )
 
-    internal val itemCount: AtomicInteger = AtomicInteger(INITIAL_ITEM_COUNT)
+    private val implementation = CommonLimitOffsetImpl(tables, this, ::convertRows)
 
-    private val observer =
-        ThreadSafeInvalidationObserver(tables = tables, onInvalidated = ::invalidate)
-
-    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> {
-        return withContext(db.getQueryContext()) {
-            observer.registerIfNecessary(db)
-            val tempCount = itemCount.get()
-            // if itemCount is < 0, then it is initial load
-            try {
-                if (tempCount == INITIAL_ITEM_COUNT) {
-                    initialLoad(params)
-                } else {
-                    nonInitialLoad(params, tempCount)
-                }
-            } catch (e: Exception) {
-                LoadResult.Error(e)
-            }
-        }
-    }
-
-    /**
-     * For the very first time that this PagingSource's [load] is called. Executes the count query
-     * (initializes [itemCount]) and db query within a transaction to ensure initial load's data
-     * integrity.
-     *
-     * For example, if the database gets updated after the count query but before the db query
-     * completes, the paging source may not invalidate in time, but this method will return data
-     * based on the original database that the count was performed on to ensure a valid initial
-     * load.
-     */
-    private suspend fun initialLoad(params: LoadParams<Int>): LoadResult<Int, Value> {
-        return db.withTransaction {
-            val tempCount = queryItemCount(sourceQuery, db)
-            itemCount.set(tempCount)
-            queryDatabase(
-                params = params,
-                sourceQuery = sourceQuery,
-                db = db,
-                itemCount = tempCount,
-                convertRows = ::convertRows
-            )
-        }
-    }
-
-    private suspend fun nonInitialLoad(
-        params: LoadParams<Int>,
-        tempCount: Int,
-    ): LoadResult<Int, Value> {
-        val loadResult =
-            queryDatabase(
-                params = params,
-                sourceQuery = sourceQuery,
-                db = db,
-                itemCount = tempCount,
-                convertRows = ::convertRows
-            )
-        // manually check if database has been updated. If so, the observer's
-        // invalidation callback will invalidate this paging source
-        db.invalidationTracker.refreshVersionsSync()
-        @Suppress("UNCHECKED_CAST")
-        return if (invalid) INVALID as LoadResult.Invalid<Int, Value> else loadResult
-    }
-
-    @NonNull protected abstract fun convertRows(cursor: Cursor): List<Value>
-
-    override fun getRefreshKey(state: PagingState<Int, Value>): Int? {
-        return state.getClippedRefreshKey()
-    }
+    actual val itemCount: Int
+        get() = implementation.itemCount.value
 
     override val jumpingSupported: Boolean
         get() = true
+
+    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> =
+        implementation.load(params)
+
+    override fun getRefreshKey(state: PagingState<Int, Value>): Int? = state.getClippedRefreshKey()
+
+    protected open fun convertRows(cursor: Cursor): List<Value> {
+        throw NotImplementedError(
+            "Unexpected call to a function with no implementation that Room is suppose to " +
+                "generate. Please file a bug at: $BUG_LINK."
+        )
+    }
+
+    protected actual open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value> {
+        return convertRows(SQLiteStatementCursor(statement, itemCount))
+    }
 }
diff --git a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/util/RoomPagingUtil.android.kt b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/util/RoomPagingUtil.android.kt
index f2a4462..525f87e 100644
--- a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/util/RoomPagingUtil.android.kt
+++ b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/util/RoomPagingUtil.android.kt
@@ -13,20 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@file:JvmName("RoomPagingUtil")
+@file:JvmMultifileClass
 
 package androidx.room.paging.util
 
 import android.database.Cursor
 import android.os.CancellationSignal
 import androidx.annotation.RestrictTo
-import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadParams
-import androidx.paging.PagingSource.LoadParams.Append
-import androidx.paging.PagingSource.LoadParams.Prepend
-import androidx.paging.PagingSource.LoadParams.Refresh
 import androidx.paging.PagingSource.LoadResult
-import androidx.paging.PagingState
 import androidx.room.RoomDatabase
 import androidx.room.RoomSQLiteQuery
 
@@ -35,65 +31,10 @@
  *
  * Any loaded data or queued loads prior to returning INVALID will be discarded
  */
+@get:Suppress("AcronymName")
+@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 val INVALID = LoadResult.Invalid<Any, Any>()
 
-/** The default itemCount value */
-const val INITIAL_ITEM_COUNT = -1
-
-/**
- * Calculates query limit based on LoadType.
- *
- * Prepend: If requested loadSize is larger than available number of items to prepend, it will query
- * with OFFSET = 0, LIMIT = prevKey
- */
-fun getLimit(params: LoadParams<Int>, key: Int): Int {
-    return when (params) {
-        is Prepend ->
-            if (key < params.loadSize) {
-                key
-            } else {
-                params.loadSize
-            }
-        else -> params.loadSize
-    }
-}
-
-/**
- * calculates query offset amount based on loadtype
- *
- * Prepend: OFFSET is calculated by counting backwards the number of items that needs to be loaded
- * before [key]. For example, if key = 30 and loadSize = 5, then offset = 25 and items in db
- * position 26-30 are loaded. If requested loadSize is larger than the number of available items to
- * prepend, OFFSET clips to 0 to prevent negative OFFSET.
- *
- * Refresh: If initialKey is supplied through Pager, Paging 3 will now start loading from initialKey
- * with initialKey being the first item. If key is supplied by [getClippedRefreshKey], the key has
- * already been adjusted to load half of the requested items before anchorPosition and the other
- * half after anchorPosition. See comments on [getClippedRefreshKey] for more details. If key
- * (regardless if from initialKey or [getClippedRefreshKey]) is larger than available items, the
- * last page will be loaded by counting backwards the loadSize before last item in database. For
- * example, this can happen if invalidation came from a large number of items dropped. i.e. in items
- * 0 - 100, items 41-80 are dropped. Depending on last viewed item, hypothetically
- * [getClippedRefreshKey] may return key = 60. If loadSize = 10, then items 31-40 will be loaded.
- */
-fun getOffset(params: LoadParams<Int>, key: Int, itemCount: Int): Int {
-    return when (params) {
-        is Prepend ->
-            if (key < params.loadSize) {
-                0
-            } else {
-                key - params.loadSize
-            }
-        is Append -> key
-        is Refresh ->
-            if (key >= itemCount) {
-                maxOf(0, itemCount - params.loadSize)
-            } else {
-                key
-            }
-    }
-}
-
 /**
  * calls RoomDatabase.query() to return a cursor and then calls convertRows() to extract and return
  * list of data
@@ -108,6 +49,7 @@
  * @param cancellationSignal the signal to cancel the query if the query hasn't yet completed
  * @param convertRows the function to iterate data with provided [Cursor] to return List<Value>
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun <Value : Any> queryDatabase(
     params: LoadParams<Int>,
     sourceQuery: RoomSQLiteQuery,
@@ -155,6 +97,7 @@
  * throws error when the column value is null, the column type is not an integral type, or the
  * integer value is outside the range [Integer.MIN_VALUE, Integer.MAX_VALUE]
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun queryItemCount(sourceQuery: RoomSQLiteQuery, db: RoomDatabase): Int {
     val countQuery = "SELECT COUNT(*) FROM ( ${sourceQuery.sql} )"
     val sqLiteQuery: RoomSQLiteQuery = RoomSQLiteQuery.acquire(countQuery, sourceQuery.argCount)
@@ -170,22 +113,3 @@
         sqLiteQuery.release()
     }
 }
-
-/**
- * Returns the key for [PagingSource] for a non-initial REFRESH load.
- *
- * To prevent a negative key, key is clipped to 0 when the number of items available before
- * anchorPosition is less than the requested amount of initialLoadSize / 2.
- */
-fun <Value : Any> PagingState<Int, Value>.getClippedRefreshKey(): Int? {
-    return when (val anchorPosition = anchorPosition) {
-        null -> null
-        /**
-         * It is unknown whether anchorPosition represents the item at the top of the screen or item
-         * at the bottom of the screen. To ensure the number of items loaded is enough to fill up
-         * the screen, half of loadSize is loaded before the anchorPosition and the other half is
-         * loaded after the anchorPosition -- anchorPosition becomes the middle item.
-         */
-        else -> maxOf(0, anchorPosition - (config.initialLoadSize / 2))
-    }
-}
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
new file mode 100644
index 0000000..2e166bc5
--- /dev/null
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.paging
+
+import androidx.annotation.RestrictTo
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadParams
+import androidx.paging.PagingSource.LoadResult
+import androidx.paging.PagingSource.LoadResult.Invalid
+import androidx.room.InvalidationTracker
+import androidx.room.RoomDatabase
+import androidx.room.RoomRawQuery
+import androidx.room.immediateTransaction
+import androidx.room.paging.util.INITIAL_ITEM_COUNT
+import androidx.room.paging.util.queryDatabase
+import androidx.room.paging.util.queryItemCount
+import androidx.room.useReaderConnection
+import androidx.sqlite.SQLiteStatement
+import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * An implementation of [PagingSource] to perform a LIMIT OFFSET query
+ *
+ * This class is used for Paging3 to perform Query and RawQuery in Room to return a PagingSource for
+ * Pager's consumption. Registers observers on tables lazily and automatically invalidates itself
+ * when data changes.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+expect abstract class LimitOffsetPagingSource<Value : Any>(
+    sourceQuery: RoomRawQuery,
+    db: RoomDatabase,
+    vararg tables: String
+) : PagingSource<Int, Value> {
+    val sourceQuery: RoomRawQuery
+    val db: RoomDatabase
+
+    val itemCount: Int
+
+    protected open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value>
+}
+
+internal class CommonLimitOffsetImpl<Value : Any>(
+    tables: Array<out String>,
+    val pagingSource: LimitOffsetPagingSource<Value>,
+    private val convertRows: (SQLiteStatement, Int) -> List<Value>
+) {
+    private val db = pagingSource.db
+    private val sourceQuery = pagingSource.sourceQuery
+    internal val itemCount = atomic(INITIAL_ITEM_COUNT)
+    private val registered = atomic(false)
+    private val observer =
+        object : InvalidationTracker.Observer(tables) {
+            override fun onInvalidated(tables: Set<String>) {
+                pagingSource.invalidate()
+            }
+        }
+
+    init {
+        pagingSource.registerInvalidatedCallback {
+            db.getCoroutineScope().launch { db.invalidationTracker.unsubscribe(observer) }
+        }
+    }
+
+    suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> {
+        return withContext(db.getCoroutineScope().coroutineContext) {
+            if (!pagingSource.invalid && registered.compareAndSet(expect = false, update = true)) {
+                db.invalidationTracker.subscribe(observer)
+            }
+            val tempCount = itemCount.value
+            // if itemCount is < 0, then it is initial load
+            try {
+                if (tempCount == INITIAL_ITEM_COUNT) {
+                    initialLoad(params)
+                } else {
+                    nonInitialLoad(params, tempCount)
+                }
+            } catch (e: Exception) {
+                LoadResult.Error(e)
+            }
+        }
+    }
+
+    /**
+     * For the very first time that this PagingSource's [load] is called. Executes the count query
+     * (initializes [itemCount]) and db query within a transaction to ensure initial load's data
+     * integrity.
+     *
+     * For example, if the database gets updated after the count query but before the db query
+     * completes, the paging source may not invalidate in time, but this method will return data
+     * based on the original database that the count was performed on to ensure a valid initial
+     * load.
+     */
+    private suspend fun initialLoad(params: LoadParams<Int>): LoadResult<Int, Value> {
+        return db.useReaderConnection { connection ->
+            connection.immediateTransaction {
+                val tempCount = queryItemCount(sourceQuery, db)
+                itemCount.value = tempCount
+                queryDatabase(
+                    params = params,
+                    sourceQuery = sourceQuery,
+                    db = db,
+                    itemCount = tempCount,
+                    convertRows = convertRows,
+                )
+            }
+        }
+    }
+
+    private suspend fun nonInitialLoad(
+        params: LoadParams<Int>,
+        tempCount: Int,
+    ): LoadResult<Int, Value> {
+        val loadResult =
+            queryDatabase(
+                params = params,
+                sourceQuery = sourceQuery,
+                db = db,
+                itemCount = tempCount,
+                convertRows = convertRows
+            )
+        // manually check if database has been updated. If so, the observer's
+        // invalidation callback will invalidate this paging source
+        db.invalidationTracker.refreshInvalidation()
+
+        @Suppress("UNCHECKED_CAST")
+        return if (pagingSource.invalid) INVALID as Invalid<Int, Value> else loadResult
+    }
+
+    companion object {
+        /**
+         * A [LoadResult] that can be returned to trigger a new generation of PagingSource
+         *
+         * Any loaded data or queued loads prior to returning INVALID will be discarded
+         */
+        val INVALID = Invalid<Any, Any>()
+
+        const val BUG_LINK =
+            "https://issuetracker.google.com/issues/new?component=413107&template=1096568"
+    }
+}
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
new file mode 100644
index 0000000..1d51cc2
--- /dev/null
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("RoomPagingUtil")
+@file:JvmMultifileClass
+
+package androidx.room.paging.util
+
+import androidx.annotation.RestrictTo
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadParams
+import androidx.paging.PagingSource.LoadParams.Append
+import androidx.paging.PagingSource.LoadParams.Prepend
+import androidx.paging.PagingSource.LoadParams.Refresh
+import androidx.paging.PagingSource.LoadResult
+import androidx.paging.PagingState
+import androidx.room.RoomDatabase
+import androidx.room.RoomRawQuery
+import androidx.room.useReaderConnection
+import androidx.sqlite.SQLiteStatement
+import kotlin.jvm.JvmMultifileClass
+import kotlin.jvm.JvmName
+
+/** The default itemCount value */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) const val INITIAL_ITEM_COUNT = -1
+
+/**
+ * Calculates query limit based on LoadType.
+ *
+ * Prepend: If requested loadSize is larger than available number of items to prepend, it will query
+ * with OFFSET = 0, LIMIT = prevKey.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun getLimit(params: LoadParams<Int>, key: Int): Int {
+    return when (params) {
+        is Prepend ->
+            if (key < params.loadSize) {
+                key
+            } else {
+                params.loadSize
+            }
+        else -> params.loadSize
+    }
+}
+
+/**
+ * Calculates query offset amount based on load type.
+ *
+ * Prepend: OFFSET is calculated by counting backwards the number of items that needs to be loaded
+ * before [key]. For example, if key = 30 and loadSize = 5, then offset = 25 and items in db
+ * position 26-30 are loaded. If requested loadSize is larger than the number of available items to
+ * prepend, OFFSET clips to 0 to prevent negative OFFSET.
+ *
+ * Refresh: If initialKey is supplied through Pager, Paging 3 will now start loading from initialKey
+ * with initialKey being the first item. If key is supplied by [getClippedRefreshKey], the key has
+ * already been adjusted to load half of the requested items before anchorPosition and the other
+ * half after anchorPosition. See comments on [getClippedRefreshKey] for more details. If key
+ * (regardless if from initialKey or [getClippedRefreshKey]) is larger than available items, the
+ * last page will be loaded by counting backwards the loadSize before last item in database. For
+ * example, this can happen if invalidation came from a large number of items dropped. i.e. in items
+ * 0 - 100, items 41-80 are dropped. Depending on last viewed item, hypothetically
+ * [getClippedRefreshKey] may return key = 60. If loadSize = 10, then items 31-40 will be loaded.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun getOffset(params: LoadParams<Int>, key: Int, itemCount: Int): Int {
+    return when (params) {
+        is Prepend ->
+            if (key < params.loadSize) {
+                0
+            } else {
+                key - params.loadSize
+            }
+        is Append -> key
+        is Refresh ->
+            if (key >= itemCount) {
+                maxOf(0, itemCount - params.loadSize)
+            } else {
+                key
+            }
+    }
+}
+
+/**
+ * Calls RoomDatabase.query() to return a cursor and then calls convertRows() to extract and return
+ * list of data.
+ *
+ * Throws [IllegalArgumentException] from CursorUtil if column does not exist.
+ *
+ * @param params load params to calculate query limit and offset
+ * @param sourceQuery user provided database query
+ * @param db the [RoomDatabase] to query from
+ * @param itemCount the db row count, triggers a new PagingSource generation if itemCount changes,
+ *   i.e. items are added / removed
+ * @param convertRows the function to iterate data with provided [androidx.sqlite.SQLiteStatement]
+ *   to return List<Value>
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+suspend fun <Value : Any> queryDatabase(
+    params: LoadParams<Int>,
+    sourceQuery: RoomRawQuery,
+    db: RoomDatabase,
+    itemCount: Int,
+    convertRows: (SQLiteStatement, Int) -> List<Value>
+): LoadResult<Int, Value> {
+    val key = params.key ?: 0
+    val limit = getLimit(params, key)
+    val offset = getOffset(params, key, itemCount)
+    val rowsCount =
+        if (limit + offset > itemCount) {
+            itemCount - offset
+        } else {
+            limit
+        }
+    val limitOffsetQuery = "SELECT * FROM ( ${sourceQuery.sql} ) LIMIT $limit OFFSET $offset"
+
+    val data: List<Value> =
+        db.useReaderConnection { connection ->
+            connection.usePrepared(limitOffsetQuery) { stmt ->
+                sourceQuery.getBindingFunction().invoke(stmt)
+                convertRows(stmt, rowsCount)
+            }
+        }
+
+    val nextPosToLoad = offset + data.size
+    val nextKey =
+        if (data.isEmpty() || data.size < limit || nextPosToLoad >= itemCount) {
+            null
+        } else {
+            nextPosToLoad
+        }
+    val prevKey = if (offset <= 0 || data.isEmpty()) null else offset
+    return LoadResult.Page(
+        data = data,
+        prevKey = prevKey,
+        nextKey = nextKey,
+        itemsBefore = offset,
+        itemsAfter = maxOf(0, itemCount - nextPosToLoad)
+    )
+}
+
+/**
+ * Returns count of requested items to calculate itemsAfter and itemsBefore for use in creating
+ * LoadResult.Page<>.
+ *
+ * Throws error when the column value is null, the column type is not an integral type, or the
+ * integer value is outside the range [Integer.MIN_VALUE, Integer.MAX_VALUE].
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+suspend fun queryItemCount(sourceQuery: RoomRawQuery, db: RoomDatabase): Int {
+    val countQuery = "SELECT COUNT(*) FROM ( ${sourceQuery.sql} )"
+    return db.useReaderConnection { connection ->
+        connection.usePrepared(countQuery) { stmt ->
+            sourceQuery.getBindingFunction().invoke(stmt)
+            if (stmt.step()) {
+                stmt.getInt(0)
+            } else {
+                0
+            }
+        }
+    }
+}
+
+/**
+ * Returns the key for [PagingSource] for a non-initial REFRESH load.
+ *
+ * To prevent a negative key, key is clipped to 0 when the number of items available before
+ * anchorPosition is less than the requested amount of initialLoadSize / 2.
+ */
+@Suppress("AutoBoxing")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <Value : Any> PagingState<Int, Value>.getClippedRefreshKey(): Int? {
+    return when (val anchorPosition = anchorPosition) {
+        null -> null
+        /**
+         * It is unknown whether anchorPosition represents the item at the top of the screen or item
+         * at the bottom of the screen. To ensure the number of items loaded is enough to fill up
+         * the screen, half of loadSize is loaded before the anchorPosition and the other half is
+         * loaded after the anchorPosition -- anchorPosition becomes the middle item.
+         */
+        else -> maxOf(0, anchorPosition - (config.initialLoadSize / 2))
+    }
+}
diff --git a/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt b/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt
new file mode 100644
index 0000000..368afb0
--- /dev/null
+++ b/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.paging
+
+import androidx.annotation.RestrictTo
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import androidx.room.RoomDatabase
+import androidx.room.RoomRawQuery
+import androidx.room.paging.CommonLimitOffsetImpl.Companion.BUG_LINK
+import androidx.room.paging.util.getClippedRefreshKey
+import androidx.sqlite.SQLiteStatement
+
+/**
+ * An implementation of [PagingSource] to perform a LIMIT OFFSET query
+ *
+ * This class is used for Paging3 to perform Query and RawQuery in Room to return a PagingSource for
+ * Pager's consumption. Registers observers on tables lazily and automatically invalidates itself
+ * when data changes.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+actual abstract class LimitOffsetPagingSource<Value : Any>
+actual constructor(
+    actual val sourceQuery: RoomRawQuery,
+    actual val db: RoomDatabase,
+    vararg tables: String,
+) : PagingSource<Int, Value>() {
+    private val implementation = CommonLimitOffsetImpl(tables, this, ::convertRows)
+
+    actual val itemCount: Int
+        get() = implementation.itemCount.value
+
+    override val jumpingSupported: Boolean
+        get() = true
+
+    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> =
+        implementation.load(params)
+
+    override fun getRefreshKey(state: PagingState<Int, Value>): Int? = state.getClippedRefreshKey()
+
+    protected actual open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value> {
+        throw NotImplementedError(
+            "Unexpected call to a function with no implementation that Room is suppose to " +
+                "generate. Please file a bug at: $BUG_LINK."
+        )
+    }
+}
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 2799d77..80ef901 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -349,6 +349,7 @@
     method public void bindNull(int index);
     method public void bindString(int index, String value);
     method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram statement);
+    method public void bindTo(androidx.sqlite.SQLiteStatement statement);
     method public void clearBindings();
     method public void close();
     method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery other);
diff --git a/room/room-runtime/bcv/native/current.txt b/room/room-runtime/bcv/native/current.txt
index 1f54a37..b7f3618 100644
--- a/room/room-runtime/bcv/native/current.txt
+++ b/room/room-runtime/bcv/native/current.txt
@@ -381,6 +381,7 @@
 
     final fun refreshAsync() // androidx.room/InvalidationTracker.refreshAsync|refreshAsync(){}[0]
     final fun stop() // androidx.room/InvalidationTracker.stop|stop(){}[0]
+    final suspend fun refreshInvalidation() // androidx.room/InvalidationTracker.refreshInvalidation|refreshInvalidation(){}[0]
     final suspend fun subscribe(androidx.room/InvalidationTracker.Observer) // androidx.room/InvalidationTracker.subscribe|subscribe(androidx.room.InvalidationTracker.Observer){}[0]
     final suspend fun unsubscribe(androidx.room/InvalidationTracker.Observer) // androidx.room/InvalidationTracker.unsubscribe|unsubscribe(androidx.room.InvalidationTracker.Observer){}[0]
 
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
index e837d59..0120d97 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.SdkSuppress
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import kotlin.test.Ignore
 import kotlin.test.Test
 import org.junit.Rule
 
@@ -41,6 +42,7 @@
     @Test
     @OptIn(ExperimentalRoomApi::class)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    @Ignore // Flaky Test b/359161892
     @Suppress("DEPRECATION") // For getRunningServices()
     fun invalidateInAnotherInstanceAutoCloser() {
         val context = ApplicationProvider.getApplicationContext<Context>()
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index b030dab..6c3b88d 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -272,6 +272,11 @@
         implementation.refreshInvalidationAsync(onRefreshScheduled, onRefreshCompleted)
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    actual suspend fun refreshInvalidation() {
+        implementation.refreshInvalidation(onRefreshScheduled, onRefreshCompleted)
+    }
+
     /** Check versions for tables, and run observers synchronously if tables have been updated. */
     @WorkerThread
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomSQLiteQuery.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomSQLiteQuery.android.kt
index 404d8e1..b0a6438 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomSQLiteQuery.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomSQLiteQuery.android.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.IntDef
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
+import androidx.sqlite.SQLiteStatement
 import androidx.sqlite.db.SupportSQLiteProgram
 import androidx.sqlite.db.SupportSQLiteQuery
 import java.util.TreeMap
@@ -92,6 +93,18 @@
         }
     }
 
+    fun bindTo(statement: SQLiteStatement) {
+        for (index in 1..argCount) {
+            when (bindingTypes[index]) {
+                NULL -> statement.bindNull(index)
+                LONG -> statement.bindLong(index, longBindings[index])
+                DOUBLE -> statement.bindDouble(index, doubleBindings[index])
+                STRING -> statement.bindText(index, requireNotNull(stringBindings[index]))
+                BLOB -> statement.bindBlob(index, requireNotNull(blobBindings[index]))
+            }
+        }
+    }
+
     override fun bindNull(index: Int) {
         bindingTypes[index] = NULL
     }
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
index d7538dc..bd5d28e 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
@@ -92,6 +92,8 @@
      */
     fun refreshAsync()
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) suspend fun refreshInvalidation()
+
     /** Stops invalidation tracker operations. */
     internal fun stop()
 
diff --git a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
index 578843ad..b1fee15 100644
--- a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
+++ b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
@@ -97,6 +97,11 @@
         implementation.refreshInvalidationAsync()
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    actual suspend fun refreshInvalidation() {
+        implementation.refreshInvalidation()
+    }
+
     /** Stops invalidation tracker operations. */
     actual fun stop() {}
 
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
index 1c9baa5..bb43e90 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
@@ -79,6 +79,7 @@
 import com.example.androidx.mediarouting.session.SessionManager;
 import com.example.androidx.mediarouting.ui.LibraryAdapter;
 import com.example.androidx.mediarouting.ui.PlaylistAdapter;
+import com.google.common.base.Function;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -614,6 +615,7 @@
 
         @Override
         public void onItemChanged(@NonNull PlaylistItem item) {
+            updateUi();
         }
     }
 
@@ -665,27 +667,28 @@
             Log.d(TAG, "onRouteSelected: requestedRoute=" + requestedRoute
                     + ", route=" + selectedRoute + ", reason=" + reason);
 
-            if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+            boolean needToRecreatePlayer =
+                    !selectedRoute.isSystemRoute() || mPlayer.isRemotePlayback();
+
+            if (needToRecreatePlayer) {
+                Player oldPlayer = mPlayer;
+                PlaylistItem currentItem = mSessionManager.getCurrentItem();
+                if (currentItem != null
+                        && currentItem.getState() != MediaItemStatus.PLAYBACK_STATE_PENDING) {
+                    // We haven't received a prepare transfer call for this. We set that up now.
+                    if (reason == MediaRouter.UNSELECT_REASON_STOPPED) {
+                        mSessionManager.pause();
+                    }
+                    mSessionManager.suspend(currentItem.getPosition());
+                }
                 mPlayer =
                         Player.createPlayerForActivity(
                                 MainActivity.this, selectedRoute, mMediaSession);
                 mPlayer.updatePresentation();
                 mSessionManager.setPlayer(mPlayer);
-            }
-            updateUi();
-        }
-
-        @Override
-        public void onRouteUnselected(@NonNull MediaRouter router, @NonNull RouteInfo route,
-                int reason) {
-            Log.d(TAG, "onRouteUnselected: route=" + route);
-
-            if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
-                mMediaSession.setActive(false);
-                mSessionManager.stop();
-
-                mPlayer.updatePresentation();
-                mPlayer.release();
+                mSessionManager.unsuspend();
+                updateUi();
+                oldPlayer.release();
             }
         }
 
@@ -800,36 +803,23 @@
                     + ", to=" + toRoute.getId());
             final PlaylistItem currentItem = getCheckedPlaylistItem();
 
-            if (currentItem != null) {
-                if (mPlayer.isRemotePlayback()) {
-                    RemotePlayer remotePlayer = (RemotePlayer) mPlayer;
-                    ListenableFuture<PlaylistItem> cacheRemoteState =
-                            remotePlayer.cacheRemoteState(currentItem);
-                    return Futures.transform(
-                            cacheRemoteState,
-                            (playlistItem) -> handleTransfer(playlistItem.getPosition(), toRoute),
-                            Runnable::run);
-                } else {
-                    long cachedPosition = getCurrentEstimatedPosition(currentItem);
-                    handleTransfer(cachedPosition, toRoute);
-                }
+            if (currentItem == null) {
+                return null; // No ongoing playback. Nothing to prepare.
             }
-            return Futures.immediateVoidFuture();
-        }
-
-        public Void handleTransfer(long currentPosition, RouteInfo destinationRoute) {
-            mSessionManager.suspend(currentPosition);
-            mMediaSession.setActive(false);
-            mPlayer.release();
-
-            mPlayer =
-                    Player.createPlayerForActivity(
-                            MainActivity.this, destinationRoute, mMediaSession);
-            mPlayer.updatePresentation();
-            mSessionManager.setPlayer(mPlayer);
-            mSessionManager.unsuspend();
-
-            return null;
+            if (mPlayer.isRemotePlayback()) {
+                RemotePlayer remotePlayer = (RemotePlayer) mPlayer;
+                ListenableFuture<PlaylistItem> cacheRemoteState =
+                        remotePlayer.cacheRemoteState(currentItem);
+                Function<PlaylistItem, Void> function =
+                        (playlistItem) -> {
+                            mSessionManager.suspend(playlistItem.getPosition());
+                            return null;
+                        };
+                return Futures.transform(cacheRemoteState, function, Runnable::run);
+            } else {
+                mSessionManager.suspend(getCurrentEstimatedPosition(currentItem));
+                return Futures.immediateVoidFuture();
+            }
         }
     }
 }
diff --git a/startup/startup-runtime/api/1.2.0-beta01.txt b/startup/startup-runtime/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..430dc8c0
--- /dev/null
+++ b/startup/startup-runtime/api/1.2.0-beta01.txt
@@ -0,0 +1,26 @@
+// Signature format: 4.0
+package androidx.startup {
+
+  public final class AppInitializer {
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.Initializer<T!>!>);
+    method public boolean isEagerlyInitialized(Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>);
+  }
+
+  public class InitializationProvider extends android.content.ContentProvider {
+    ctor public InitializationProvider();
+    method public final int delete(android.net.Uri, String?, String![]?);
+    method public final String? getType(android.net.Uri);
+    method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public final boolean onCreate();
+    method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
+  public interface Initializer<T> {
+    method public T create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>!> dependencies();
+  }
+
+}
+
diff --git a/startup/startup-runtime/api/res-1.2.0-beta01.txt b/startup/startup-runtime/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/startup/startup-runtime/api/res-1.2.0-beta01.txt
diff --git a/startup/startup-runtime/api/restricted_1.2.0-beta01.txt b/startup/startup-runtime/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..430dc8c0
--- /dev/null
+++ b/startup/startup-runtime/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,26 @@
+// Signature format: 4.0
+package androidx.startup {
+
+  public final class AppInitializer {
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.Initializer<T!>!>);
+    method public boolean isEagerlyInitialized(Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>);
+  }
+
+  public class InitializationProvider extends android.content.ContentProvider {
+    ctor public InitializationProvider();
+    method public final int delete(android.net.Uri, String?, String![]?);
+    method public final String? getType(android.net.Uri);
+    method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public final boolean onCreate();
+    method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
+  public interface Initializer<T> {
+    method public T create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<? extends java.lang.Object!>!>!> dependencies();
+  }
+
+}
+
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 8d64456..ea38e1a 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -366,7 +366,7 @@
 
   public final class LazyColumnDslKt {
     method public static inline <T> void items(androidx.wear.compose.foundation.lazy.LazyColumnScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.LazyColumnItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.LazyColumnScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.LazyColumnItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   @androidx.wear.compose.foundation.lazy.LazyColumnScopeMarker public sealed interface LazyColumnItemScope {
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 8d64456..ea38e1a 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -366,7 +366,7 @@
 
   public final class LazyColumnDslKt {
     method public static inline <T> void items(androidx.wear.compose.foundation.lazy.LazyColumnScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.LazyColumnItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.LazyColumnScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,? extends java.lang.Object?> contentType, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.LazyColumnItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   @androidx.wear.compose.foundation.lazy.LazyColumnScopeMarker public sealed interface LazyColumnItemScope {
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index 5748cf9..aa8f032 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -32,14 +32,14 @@
 }
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.6.0")
-    api("androidx.compose.ui:ui:1.7.0-beta01")
-    api("androidx.compose.ui:ui-text:1.7.0-beta01")
-    api("androidx.compose.runtime:runtime:1.6.0")
+    api("androidx.compose.foundation:foundation:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
+    api("androidx.compose.ui:ui-text:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.foundation:foundation-layout:1.6.0")
-    implementation("androidx.compose.ui:ui-util:1.7.0-beta01")
+    implementation("androidx.compose.foundation:foundation-layout:1.7.0-rc01")
+    implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
     implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
     implementation("androidx.core:core:1.12.0")
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/LazyColumnTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/LazyColumnTest.kt
index 761fc3d..03d8961 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/LazyColumnTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/LazyColumnTest.kt
@@ -230,6 +230,27 @@
     }
 
     @Test
+    fun changeDataIndexed() {
+        val dataLists = listOf((1..3).toList(), (4..8).toList(), (3..4).toList())
+        var dataModel by mutableStateOf(emptyList<Int>())
+        val tag = "List"
+        rule.setContentWithTestViewConfiguration {
+            LazyColumn(Modifier.testTag(tag)) {
+                itemsIndexed(dataModel) { index, element -> BasicText("$index - $element") }
+            }
+        }
+
+        for (data in dataLists) {
+            rule.runOnIdle { dataModel = data }
+
+            // Confirm the children's content
+            for (index in data.indices) {
+                rule.onNodeWithText("$index - ${data[index]}").assertIsDisplayed()
+            }
+        }
+    }
+
+    @Test
     fun removalWithMutableStateListOf() {
         val items = mutableStateListOf("1", "2", "3")
 
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnDsl.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnDsl.kt
index 4a77593..c602876 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnDsl.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnDsl.kt
@@ -17,8 +17,6 @@
 package androidx.wear.compose.foundation.lazy
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.lazy.LazyItemScope
-import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent
 import androidx.compose.foundation.lazy.layout.MutableIntervalList
 import androidx.compose.runtime.Composable
@@ -141,11 +139,11 @@
  *   will be considered compatible.
  * @param itemContent the content displayed by a single item
  */
-inline fun <T> LazyListScope.itemsIndexed(
+inline fun <T> LazyColumnScope.itemsIndexed(
     items: List<T>,
     noinline key: ((index: Int, item: T) -> Any)? = null,
     crossinline contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
-    crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
+    crossinline itemContent: @Composable LazyColumnItemScope.(index: Int, item: T) -> Unit
 ) =
     items(
         count = items.size,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
index d895dbc..bfda803 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
@@ -26,6 +26,8 @@
     val anchorItemIndex: Int,
     /** The offset of the anchor item from the top of screen. */
     val anchorItemScrollOffset: Int,
+    /** Last known height for the anchor item or negative number if it hasn't been measured. */
+    val lastMeasuredItemHeight: Int,
     /** Layout information for the visible items. */
     val visibleItems: List<LazyColumnVisibleItemInfo>,
     /** see [LazyColumnLayoutInfo.totalItemsCount] */
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
index 154d414..bc40a2a 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
+import kotlin.math.abs
 import kotlin.math.roundToInt
 
 private interface MeasuredItemProvider {
@@ -68,6 +69,7 @@
     containerConstraints: Constraints,
     anchorItemIndex: Int,
     anchorItemScrollOffset: Int,
+    lastMeasuredItemHeight: Int,
     scrollToBeConsumed: Float,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyColumnMeasureResult {
@@ -77,6 +79,7 @@
             anchorItemScrollOffset = 0,
             visibleItems = emptyList(),
             totalItemsCount = 0,
+            lastMeasuredItemHeight = Int.MIN_VALUE,
             measureResult = layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {}
         )
     }
@@ -85,17 +88,24 @@
 
     // Place center item
     val centerItem =
-        measuredItemProvider.upwardMeasuredItem(
-            anchorItemIndex,
-            anchorItemScrollOffset + containerConstraints.maxHeight / 2
-        )
+        if (lastMeasuredItemHeight > 0) {
+            measuredItemProvider.downwardMeasuredItem(
+                anchorItemIndex,
+                anchorItemScrollOffset - lastMeasuredItemHeight + containerConstraints.maxHeight / 2
+            )
+        } else {
+            measuredItemProvider.upwardMeasuredItem(
+                anchorItemIndex,
+                anchorItemScrollOffset + containerConstraints.maxHeight / 2
+            )
+        }
     centerItem.offset += scrollToBeConsumed.roundToInt()
     visibleItems.add(centerItem)
 
     var bottomOffset = centerItem.offset + centerItem.height + itemSpacing
     var bottomPassIndex = anchorItemIndex + 1
 
-    while (bottomOffset < containerConstraints.maxHeight && bottomPassIndex < itemsCount) {
+    while (bottomOffset < containerConstraints.maxHeight + 50 && bottomPassIndex < itemsCount) {
         val item = measuredItemProvider.downwardMeasuredItem(bottomPassIndex, bottomOffset)
         bottomOffset += item.height + itemSpacing
         visibleItems.add(item)
@@ -118,13 +128,15 @@
             anchorItemScrollOffset = 0,
             visibleItems = emptyList(),
             totalItemsCount = 0,
+            lastMeasuredItemHeight = Int.MIN_VALUE,
             measureResult = layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {}
         )
     }
 
     val anchorItem =
-        visibleItems.lastOrNull { it.offset + it.height <= containerConstraints.maxHeight / 2 }
-            ?: visibleItems[visibleItems.size / 2]
+        visibleItems.minByOrNull {
+            abs(it.offset + it.height / 2 - containerConstraints.maxHeight / 2)
+        } ?: centerItem
 
     return LazyColumnMeasureResult(
         anchorItemIndex = anchorItem.index,
@@ -140,6 +152,7 @@
                 )
             },
         totalItemsCount = itemsCount,
+        lastMeasuredItemHeight = anchorItem.height,
         measureResult =
             layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {
                 visibleItems.fastForEach { it.place(this) }
@@ -152,7 +165,7 @@
     override val offset: Int,
     override val height: Int,
     override val scrollProgress: LazyColumnItemScrollProgress
-) : LazyColumnVisibleItemInfo {}
+) : LazyColumnVisibleItemInfo
 
 private fun bottomItemScrollProgress(
     offset: Int,
@@ -195,7 +208,11 @@
                         index: Int,
                         offset: Int
                     ): LazyColumnMeasuredItem {
-                        val placeables = measure(index, containerConstraints)
+                        val placeables =
+                            measure(
+                                index,
+                                containerConstraints.copy(maxHeight = Constraints.Infinity)
+                            )
                         // TODO(artemiy): Add support for multiple items.
                         val content = placeables.last()
                         val scrollProgress =
@@ -219,7 +236,11 @@
                         index: Int,
                         offset: Int
                     ): LazyColumnMeasuredItem {
-                        val placeables = measure(index, containerConstraints)
+                        val placeables =
+                            measure(
+                                index,
+                                containerConstraints.copy(maxHeight = Constraints.Infinity)
+                            )
                         // TODO(artemiy): Add support for multiple items.
                         val content = placeables.last()
                         val scrollProgress =
@@ -252,6 +273,7 @@
                         scrollToBeConsumed = state.scrollToBeConsumed,
                         anchorItemIndex = state.anchorItemIndex,
                         anchorItemScrollOffset = state.anchorItemScrollOffset,
+                        lastMeasuredItemHeight = state.lastMeasuredAnchorItemHeight,
                         layout = { width, height, placement ->
                             layout(
                                 containerConstraints.constrainWidth(width),
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
index b81078e..5b71249 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
@@ -65,6 +65,9 @@
     internal var anchorItemScrollOffset by mutableIntStateOf(0)
         private set
 
+    internal var lastMeasuredAnchorItemHeight: Int = Int.MIN_VALUE
+        private set
+
     internal var remeasurement: Remeasurement? = null
         private set
 
@@ -81,6 +84,7 @@
         scrollToBeConsumed = 0f
         anchorItemIndex = measureResult.anchorItemIndex
         anchorItemScrollOffset = measureResult.anchorItemScrollOffset
+        lastMeasuredAnchorItemHeight = measureResult.lastMeasuredItemHeight
         layoutInfo =
             LazyColumnLayoutInfoImpl(
                 visibleItems = measureResult.visibleItems,
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index efc7042..e380531 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -33,16 +33,16 @@
 }
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.7.0-beta02")
-    api("androidx.compose.ui:ui:1.6.0")
-    api("androidx.compose.ui:ui-text:1.6.0")
-    api("androidx.compose.runtime:runtime:1.6.0")
+    api("androidx.compose.foundation:foundation:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
+    api("androidx.compose.ui:ui-text:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.animation:animation:1.6.0")
-    implementation("androidx.compose.material:material-icons-core:1.6.0")
-    implementation("androidx.compose.material:material-ripple:1.6.0")
-    implementation("androidx.compose.ui:ui-util:1.6.0")
+    implementation("androidx.compose.animation:animation:1.7.0-rc01")
+    implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
+    implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
+    implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
     implementation(project(":wear:compose:compose-foundation"))
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index 927c577..864af0d 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -31,17 +31,17 @@
 }
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.6.0")
-    api("androidx.compose.ui:ui:1.6.0")
-    api("androidx.compose.ui:ui-text:1.6.0")
-    api("androidx.compose.runtime:runtime:1.6.0")
+    api("androidx.compose.foundation:foundation:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
+    api("androidx.compose.ui:ui-text:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
     api(project(":wear:compose:compose-foundation"))
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.animation:animation:1.6.0")
-    implementation("androidx.compose.material:material-icons-core:1.6.0")
-    implementation("androidx.compose.material:material-ripple:1.7.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.6.0")
+    implementation("androidx.compose.animation:animation:1.7.0-rc01")
+    implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
+    implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
+    implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
     implementation(project(":wear:compose:compose-material-core"))
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.lifecycle:lifecycle-common:2.7.0")
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 265b837..68c71a3 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -485,7 +485,7 @@
   }
 
   public final class PickerGroupItem {
-    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
+    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional float spacing, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
     method public String? getContentDescription();
     method public androidx.compose.ui.focus.FocusRequester? getFocusRequester();
     method public androidx.compose.ui.Modifier getModifier();
@@ -493,6 +493,7 @@
     method public kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> getOption();
     method public androidx.wear.compose.material3.PickerState getPickerState();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? getReadOnlyLabel();
+    method public float getSpacing();
     property public final String? contentDescription;
     property public final androidx.compose.ui.focus.FocusRequester? focusRequester;
     property public final androidx.compose.ui.Modifier modifier;
@@ -500,6 +501,7 @@
     property public final kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> option;
     property public final androidx.wear.compose.material3.PickerState pickerState;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel;
+    property public final float spacing;
   }
 
   public final class PickerGroupKt {
@@ -522,7 +524,7 @@
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float spacing, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material3.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -1070,15 +1072,58 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  @androidx.compose.runtime.Immutable public final class TimePickerColors {
+    ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+    method public long getConfirmButtonContainerColor();
+    method public long getConfirmButtonContentColor();
+    method public long getPickerLabelColor();
+    method public long getSelectedPickerContentColor();
+    method public long getSeparatorColor();
+    method public long getUnselectedPickerContentColor();
+    property public final long confirmButtonContainerColor;
+    property public final long confirmButtonContentColor;
+    property public final long pickerLabelColor;
+    property public final long selectedPickerContentColor;
+    property public final long separatorColor;
+    property public final long unselectedPickerContentColor;
+  }
+
+  public final class TimePickerDefaults {
+    method @androidx.compose.runtime.Composable public int getTimePickerType();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long separatorColor, optional long pickerLabelColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+    property @androidx.compose.runtime.Composable public final int timePickerType;
+    field public static final androidx.wear.compose.material3.TimePickerDefaults INSTANCE;
+  }
+
+  public final class TimePickerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
+    field public static final androidx.wear.compose.material3.TimePickerType.Companion Companion;
+  }
+
+  public static final class TimePickerType.Companion {
+    method public int getHoursMinutes24H();
+    method public int getHoursMinutesAmPm12H();
+    method public int getHoursMinutesSeconds24H();
+    property public final int HoursMinutes24H;
+    property public final int HoursMinutesAmPm12H;
+    property public final int HoursMinutesSeconds24H;
+  }
+
   public interface TimeSource {
     method @androidx.compose.runtime.Composable public String currentTime();
   }
 
   public final class TimeTextDefaults {
+    method public float getAutoTextWeight();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimeSource rememberTimeSource(String timeFormat);
     method @androidx.compose.runtime.Composable public String timeFormat();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
+    property public final float AutoTextWeight;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
     field public static final float MaxSweepAngle = 70.0f;
@@ -1093,7 +1138,7 @@
   public abstract sealed class TimeTextScope {
     method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
-    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style);
+    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
     method public abstract void time();
   }
 
@@ -1198,3 +1243,13 @@
 
 }
 
+package androidx.wear.compose.material3.lazy {
+
+  public final class LazyColumnScrollTransformModifiersKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier scrollTransform(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope);
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier scrollTransform(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope, long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+    method public static androidx.compose.ui.Modifier targetMorphingHeight(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope);
+  }
+
+}
+
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 265b837..68c71a3 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -485,7 +485,7 @@
   }
 
   public final class PickerGroupItem {
-    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
+    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional float spacing, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
     method public String? getContentDescription();
     method public androidx.compose.ui.focus.FocusRequester? getFocusRequester();
     method public androidx.compose.ui.Modifier getModifier();
@@ -493,6 +493,7 @@
     method public kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> getOption();
     method public androidx.wear.compose.material3.PickerState getPickerState();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? getReadOnlyLabel();
+    method public float getSpacing();
     property public final String? contentDescription;
     property public final androidx.compose.ui.focus.FocusRequester? focusRequester;
     property public final androidx.compose.ui.Modifier modifier;
@@ -500,6 +501,7 @@
     property public final kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> option;
     property public final androidx.wear.compose.material3.PickerState pickerState;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel;
+    property public final float spacing;
   }
 
   public final class PickerGroupKt {
@@ -522,7 +524,7 @@
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float spacing, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material3.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -1070,15 +1072,58 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  @androidx.compose.runtime.Immutable public final class TimePickerColors {
+    ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+    method public long getConfirmButtonContainerColor();
+    method public long getConfirmButtonContentColor();
+    method public long getPickerLabelColor();
+    method public long getSelectedPickerContentColor();
+    method public long getSeparatorColor();
+    method public long getUnselectedPickerContentColor();
+    property public final long confirmButtonContainerColor;
+    property public final long confirmButtonContentColor;
+    property public final long pickerLabelColor;
+    property public final long selectedPickerContentColor;
+    property public final long separatorColor;
+    property public final long unselectedPickerContentColor;
+  }
+
+  public final class TimePickerDefaults {
+    method @androidx.compose.runtime.Composable public int getTimePickerType();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long separatorColor, optional long pickerLabelColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+    property @androidx.compose.runtime.Composable public final int timePickerType;
+    field public static final androidx.wear.compose.material3.TimePickerDefaults INSTANCE;
+  }
+
+  public final class TimePickerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
+    field public static final androidx.wear.compose.material3.TimePickerType.Companion Companion;
+  }
+
+  public static final class TimePickerType.Companion {
+    method public int getHoursMinutes24H();
+    method public int getHoursMinutesAmPm12H();
+    method public int getHoursMinutesSeconds24H();
+    property public final int HoursMinutes24H;
+    property public final int HoursMinutesAmPm12H;
+    property public final int HoursMinutesSeconds24H;
+  }
+
   public interface TimeSource {
     method @androidx.compose.runtime.Composable public String currentTime();
   }
 
   public final class TimeTextDefaults {
+    method public float getAutoTextWeight();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimeSource rememberTimeSource(String timeFormat);
     method @androidx.compose.runtime.Composable public String timeFormat();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
+    property public final float AutoTextWeight;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
     field public static final float MaxSweepAngle = 70.0f;
@@ -1093,7 +1138,7 @@
   public abstract sealed class TimeTextScope {
     method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
-    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style);
+    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
     method public abstract void time();
   }
 
@@ -1198,3 +1243,13 @@
 
 }
 
+package androidx.wear.compose.material3.lazy {
+
+  public final class LazyColumnScrollTransformModifiersKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier scrollTransform(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope);
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier scrollTransform(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope, long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+    method public static androidx.compose.ui.Modifier targetMorphingHeight(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.LazyColumnItemScope scope);
+  }
+
+}
+
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index 4068a7f..f103f85 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -32,18 +32,18 @@
 }
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.6.0")
-    api("androidx.compose.ui:ui:1.6.0")
-    api("androidx.compose.ui:ui-text:1.6.0")
-    api("androidx.compose.runtime:runtime:1.6.0")
+    api("androidx.compose.foundation:foundation:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
+    api("androidx.compose.ui:ui-text:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
     api(project(":wear:compose:compose-foundation"))
 
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesCore)
-    implementation("androidx.compose.animation:animation:1.6.0")
-    implementation("androidx.compose.material:material-icons-core:1.6.0")
-    implementation("androidx.compose.material:material-ripple:1.7.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.6.0")
+    implementation("androidx.compose.animation:animation:1.7.0-rc01")
+    implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
+    implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
+    implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
     implementation(project(":wear:compose:compose-material-core"))
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.graphics:graphics-shapes:1.0.0-beta01")
@@ -52,6 +52,7 @@
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(project(":test:screenshot:screenshot"))
+    androidTestImplementation(libs.testParameterInjector)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":wear:compose:compose-material3-samples"))
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/LazyColumnDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/LazyColumnDemo.kt
new file mode 100644
index 0000000..55b61c7
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/LazyColumnDemo.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.demos
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.LazyColumn
+import androidx.wear.compose.foundation.lazy.items
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.lazy.scrollTransform
+import androidx.wear.compose.material3.lazy.targetMorphingHeight
+
+@Composable
+fun LazyColumnNotificationsDemo() {
+    MaterialTheme {
+        Box(modifier = Modifier.aspectRatio(1f).background(Color.Black)) {
+            LazyColumn(
+                modifier = Modifier.padding(horizontal = 10.dp),
+            ) {
+                item { ListHeader { Text("Notifications") } }
+                items(notificationList) { notification ->
+                    Column(
+                        modifier =
+                            Modifier.scrollTransform(
+                                    this@items,
+                                    backgroundColor = Color.DarkGray,
+                                    shape = RoundedCornerShape(20.dp)
+                                )
+                                .padding(10.dp)
+                    ) {
+                        Text(
+                            notification.title,
+                            fontWeight = FontWeight.Bold,
+                            style = MaterialTheme.typography.labelLarge,
+                            modifier = Modifier.targetMorphingHeight(this@items)
+                        )
+                        Text(notification.body)
+                    }
+                }
+            }
+        }
+    }
+}
+
+private data class NotificationItem(val title: String, val body: String)
+
+private val notificationList =
+    listOf(
+        NotificationItem(
+            "☕ Coffee Break?",
+            "Step away from the screen and grab a pick-me-up. Step away from the screen and grab a pick-me-up."
+        ),
+        NotificationItem("🌟 You're Awesome!", "Just a little reminder in case you forgot 😊"),
+        NotificationItem("👀 Did you know?", "Check out [app name]'s latest feature update."),
+        NotificationItem("📅 Appointment Time", "Your meeting with [name] is in 15 minutes."),
+        NotificationItem("📦 Package On the Way", "Your order is expected to arrive today!"),
+        NotificationItem("🤔 Trivia Time!", "Test your knowledge with a quick quiz on [app name]."),
+        NotificationItem(
+            "🌤️ Weather Update",
+            "Don't forget your umbrella - rain is likely this afternoon."
+        ),
+        NotificationItem("🤝 Connect with [name]", "They sent you a message on [social platform]."),
+        NotificationItem("🧘‍♀️ Time to Breathe", "Take a 5-minute mindfulness break."),
+        NotificationItem("🌟 Goal Achieved!", "You completed your daily step goal. Way to go!"),
+        NotificationItem("💡 New Idea!", "Got a spare moment? Jot down a quick note."),
+        NotificationItem("👀 Photo Memories", "Rediscover photos from this day last year."),
+        NotificationItem("🚗 Parking Reminder", "Your parking meter expires in 1 hour."),
+        NotificationItem("🎧 Playlist Time", "Your daily mix on [music app] is ready."),
+        NotificationItem(
+            "🎬 Movie Night?",
+            "New releases are out on your favorite streaming service. New releases are out on your favorite streaming service."
+        ),
+        NotificationItem("📚 Reading Time", "Pick up where you left off in your current book."),
+        NotificationItem("🤔 Something to Ponder", "Here's a thought-provoking quote for today..."),
+        NotificationItem("⏰ Time for [task]", "Remember to [brief description]."),
+        NotificationItem("💧 Stay Hydrated!", "Have you had a glass of water recently?"),
+        NotificationItem("👀 Game Update Available", "Your favorite game has new content!"),
+        NotificationItem("🌎 Learn Something New", "Fact of the day: [Insert a fun fact]."),
+        NotificationItem(
+            "☀️ Step Outside",
+            "Get some fresh air and sunshine for a quick energy boost"
+        ),
+        NotificationItem("🎉 It's [friend's name]'s Birthday!", "Don't forget to send a message."),
+        NotificationItem("✈️ Travel Inspiration", "Where's your dream travel destination?"),
+        NotificationItem("😋 Recipe Time", "Find a new recipe to try on [recipe website]."),
+        NotificationItem("👀 Explore!", "[App name] has a hidden feature - can you find it?"),
+        NotificationItem("💰 Savings Update", "You're [percent] closer to your savings goal!"),
+        NotificationItem("🌟 Daily Challenge", "Try today's mini-challenge on [app name]."),
+        NotificationItem("💤 Bedtime Approaching", "Start winding down for a good night's sleep."),
+        NotificationItem("🤝 Team Update", "[Team member] posted on your project board."),
+        NotificationItem("🌿 Plant Care", "Time to water your [plant type]."),
+        NotificationItem("🎮 Game Break?", "Take a 10-minute break with your favorite game."),
+        NotificationItem("🗣️  Your Voice Matters", "New poll available on [topic/app]."),
+        NotificationItem("🎨 Get Creative", "Doodle, draw, or paint for a few minutes."),
+        NotificationItem("❓Ask a Question", "What's something that's been on your mind?"),
+        NotificationItem("🔍 Search Time", "Research a topic that interests you."),
+        NotificationItem(
+            "🤝 Help Someone Out",
+            "Is there a small way you can assist someone today?"
+        ),
+        NotificationItem("🐾 Pet Appreciation", "Give your furry friend some extra love."),
+        NotificationItem("📝 Journal Time", "Take 5 minutes to jot down your thoughts.")
+    )
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
index 83f1564..7149870 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
@@ -16,27 +16,88 @@
 
 package androidx.wear.compose.material3.demos
 
+import android.os.Build
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.Picker
 import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimePicker
+import androidx.wear.compose.material3.TimePickerType
 import androidx.wear.compose.material3.rememberPickerState
 import androidx.wear.compose.material3.samples.AutoCenteringPickerGroup
 import androidx.wear.compose.material3.samples.PickerAnimateScrollToOption
 import androidx.wear.compose.material3.samples.PickerGroupSample
 import androidx.wear.compose.material3.samples.SimplePicker
+import androidx.wear.compose.material3.samples.TimePickerSample
+import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
 
 val PickerDemos =
     listOf(
+        // Requires API level 26 or higher due to java.time dependency.
+        *(if (Build.VERSION.SDK_INT >= 26)
+            arrayOf(
+                ComposableDemo("Time HH:MM:SS") { TimePickerWithSecondsSample() },
+                ComposableDemo("Time HH:MM") {
+                    var showTimePicker by remember { mutableStateOf(true) }
+                    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+                    val formatter = DateTimeFormatter.ofPattern("HH:mm")
+                    if (showTimePicker) {
+                        TimePicker(
+                            onTimePicked = {
+                                timePickerTime = it
+                                showTimePicker = false
+                            },
+                            timePickerType = TimePickerType.HoursMinutes24H,
+                            // Initialize with last picked time on reopen
+                            initialTime = timePickerTime
+                        )
+                    } else {
+                        Column(
+                            modifier = Modifier.fillMaxSize(),
+                            verticalArrangement = Arrangement.Center,
+                            horizontalAlignment = Alignment.CenterHorizontally
+                        ) {
+                            Text("Selected Time")
+                            Spacer(Modifier.height(12.dp))
+                            Button(
+                                onClick = { showTimePicker = true },
+                                label = { Text(timePickerTime.format(formatter)) },
+                                icon = {
+                                    Icon(
+                                        imageVector = Icons.Filled.Edit,
+                                        contentDescription = "Edit"
+                                    )
+                                },
+                            )
+                        }
+                    }
+                },
+                ComposableDemo("Time 12 Hour") { TimePickerWith12HourClockSample() },
+                ComposableDemo("Time System time format") { TimePickerSample() },
+            )
+        else emptyArray<ComposableDemo>()),
         ComposableDemo("Simple Picker") { SimplePicker() },
         ComposableDemo("No gradient") { PickerWithoutGradient() },
         ComposableDemo("Animate picker change") { PickerAnimateScrollToOption() },
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index 30e52196..63cf38f 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -161,7 +161,11 @@
                     emptyList()
                 }
             ),
-            ComposableDemo("Settings Demo") { SettingsDemo() }
+            ComposableDemo("Settings Demo") { SettingsDemo() },
+            Material3DemoCategory(
+                title = "LazyColumn",
+                listOf(ComposableDemo("Notifications") { LazyColumnNotificationsDemo() })
+            )
         )
     )
 
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/LazyColumnScrollTransformSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/LazyColumnScrollTransformSample.kt
new file mode 100644
index 0000000..fa93ea6
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/LazyColumnScrollTransformSample.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.LazyColumn
+import androidx.wear.compose.foundation.lazy.items
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.lazy.scrollTransform
+import androidx.wear.compose.material3.lazy.targetMorphingHeight
+
+@Sampled
+@Preview
+@Composable
+fun LazyColumnScalingMorphingEffectSample() {
+    val allIngredients = listOf("2 eggs", "tomato", "cheese", "bread")
+
+    LazyColumn(modifier = Modifier.background(Color.Black).padding(horizontal = 10.dp)) {
+        item {
+            // No modifier is applied - no Material 3 Motion.
+            ListHeader { Text("Ingredients") }
+        }
+
+        items(allIngredients) { ingredient ->
+            Text(
+                ingredient,
+                color = MaterialTheme.colorScheme.onSurface,
+                style = MaterialTheme.typography.bodyLarge,
+                modifier =
+                    Modifier.fillMaxWidth()
+                        // Apply Material 3 Motion transformations.
+                        .scrollTransform(
+                            this,
+                            backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
+                            shape = RoundedCornerShape(10.dp)
+                        )
+                        .padding(10.dp)
+            )
+        }
+    }
+}
+
+@Sampled
+@Preview
+@Composable
+fun LazyColumnTargetMorphingHeightSample() {
+    data class MenuItem(val title: String, val price: Float)
+
+    val drinks =
+        listOf(
+            MenuItem("Cappuccino", 2.5f),
+            MenuItem("Late", 3f),
+            MenuItem("Flat White", 3.2f),
+            MenuItem("Americano", 1.5f),
+            MenuItem("Black tea", 2f),
+            MenuItem("London fog", 2.6f),
+        )
+
+    MaterialTheme {
+        Box(modifier = Modifier.aspectRatio(1f).background(Color.Black)) {
+            LazyColumn(
+                modifier = Modifier.padding(horizontal = 10.dp),
+            ) {
+                item {
+                    // No modifier is applied - no Material 3 Motion transformations.
+                    ListHeader { Text("Drinks", style = MaterialTheme.typography.labelLarge) }
+                }
+                items(drinks) { notification ->
+                    Column(
+                        modifier =
+                            Modifier.fillMaxWidth()
+                                // Apply Material 3 Motion effect.
+                                .scrollTransform(
+                                    this@items,
+                                    backgroundColor = Color.DarkGray,
+                                    shape = RoundedCornerShape(20.dp),
+                                )
+                                .padding(horizontal = 10.dp)
+                    ) {
+                        Text(
+                            notification.title,
+                            fontWeight = FontWeight.Bold,
+                            style = MaterialTheme.typography.labelLarge,
+                            // Morphing is focusing on the title.
+                            modifier = Modifier.targetMorphingHeight(this@items)
+                        )
+                        // Price is revealed after the morph.
+                        Text("$${notification.price}")
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
index 771e384..adc77cc 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
@@ -64,7 +64,7 @@
     val contentDescription by remember { derivedStateOf { "${state.selectedOption + 1}" } }
     Picker(
         state = state,
-        separation = 4.dp,
+        spacing = 4.dp,
         contentDescription = contentDescription,
     ) {
         Button(
@@ -84,7 +84,7 @@
     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
         Picker(
             state = state,
-            separation = 4.dp,
+            spacing = 4.dp,
             contentDescription = contentDescription,
         ) {
             Button(
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
new file mode 100644
index 0000000..78cc200
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimePicker
+import androidx.wear.compose.material3.TimePickerType
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+@Sampled
+@Composable
+fun TimePickerSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter =
+        DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
+            .withLocale(LocalConfiguration.current.locales[0])
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun TimePickerWithSecondsSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            timePickerType = TimePickerType.HoursMinutesSeconds24H,
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun TimePickerWith12HourClockSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter = DateTimeFormatter.ofPattern("hh:mm a")
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            timePickerType = TimePickerType.HoursMinutesAmPm12H,
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LazyColumnMorphingHeightTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LazyColumnMorphingHeightTest.kt
new file mode 100644
index 0000000..28b8a8a
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LazyColumnMorphingHeightTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.LazyColumn
+import androidx.wear.compose.material3.lazy.minMorphingHeightConsumer
+import androidx.wear.compose.material3.lazy.targetMorphingHeight
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+class LazyColumnMorphingHeightTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun targetMorphingHeightReportedCorrectly() {
+        val expectedMorphingHeight = 10
+
+        var actualMorphingHeight: Int? = null
+
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(
+                        modifier =
+                            Modifier.size(100.dp).minMorphingHeightConsumer {
+                                actualMorphingHeight = it
+                            }
+                    ) {
+                        Spacer(
+                            modifier =
+                                Modifier.height(
+                                        with(LocalDensity.current) { expectedMorphingHeight.toDp() }
+                                    )
+                                    .targetMorphingHeight(this@item)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        assertThat(actualMorphingHeight).isEqualTo(expectedMorphingHeight)
+    }
+
+    @Test
+    fun noValueIsReportedWhenNoComposableAnnotatedWithTargetMorphingHeight() {
+        var initialReportedMorphingHeight: Int? = 11
+
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(
+                        modifier =
+                            Modifier.size(100.dp).minMorphingHeightConsumer {
+                                initialReportedMorphingHeight = null
+                            }
+                    ) {
+                        Spacer(modifier = Modifier.height(20.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        // New morphing height should not be reported.
+        assertThat(initialReportedMorphingHeight).isNotNull()
+    }
+
+    @Test
+    fun morphingHeightIsReportedToAncestors() {
+        var reportedMorphingHeight: Int? = null
+
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(
+                        modifier =
+                            Modifier.size(100.dp)
+                                .minMorphingHeightConsumer { reportedMorphingHeight = it }
+                                .targetMorphingHeight(scope = this@item)
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        assertThat(reportedMorphingHeight).isNotNull()
+    }
+
+    @Test
+    fun morphingHeightIsNotReportedToDescendants() {
+        var reportedMorphingHeight: Int? = null
+
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(
+                        modifier =
+                            Modifier.size(100.dp)
+                                // Misuse of API - targetMorphingHeight must be applied after
+                                // consumer.
+                                .targetMorphingHeight(scope = this@item)
+                                .minMorphingHeightConsumer { reportedMorphingHeight = it }
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        assertThat(reportedMorphingHeight).isNull()
+    }
+
+    @Test
+    fun morphingHeightIsNotReportedToMultipleAncestors() {
+        var reportedMorphingHeight: Int? = null
+        val expectedMorphingHeight = 10
+
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(
+                        modifier =
+                            Modifier.size(
+                                    with(LocalDensity.current) { expectedMorphingHeight.toDp() }
+                                )
+                                // This consumer's callback is ignored as traversal stops.
+                                .minMorphingHeightConsumer { reportedMorphingHeight = null }
+                                // This consumer receives the callback as it is the first one.
+                                .minMorphingHeightConsumer { reportedMorphingHeight = it }
+                                .targetMorphingHeight(scope = this@item)
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        assertThat(reportedMorphingHeight).isEqualTo(expectedMorphingHeight)
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index addc388..2ea8c78 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -69,11 +69,16 @@
 import androidx.test.screenshot.AndroidXScreenshotTestRule
 import kotlin.math.abs
 import org.junit.Assert
+import org.junit.rules.TestName
 
 /** Constant to emulate very big but finite constraints */
 val BigTestMaxWidth = 5000.dp
 val BigTestMaxHeight = 5000.dp
 
+/** Screen size constants for screenshot tests */
+val SCREEN_SIZE_SMALL = 192
+val SCREEN_SIZE_LARGE = 228
+
 internal const val TEST_TAG = "test-item"
 
 @Composable
@@ -218,6 +223,17 @@
     }
 }
 
+enum class ScreenSize(val size: Int) {
+    SMALL(SCREEN_SIZE_SMALL),
+    LARGE(SCREEN_SIZE_LARGE)
+}
+
+/**
+ * Valid characters for golden identifiers are [A-Za-z0-9_-] TestParameterInjector adds '[' +
+ * parameter_values + ']' to the test name.
+ */
+fun TestName.methodNameWithValidCharacters(): String = methodName.replace("[", "_").replace("]", "")
+
 /**
  * Asserts that the layout of this node has height equal to [expectedHeight].
  *
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
index d579f24..6a66a954 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
@@ -212,7 +212,7 @@
                     modifier =
                         Modifier.testTag(TEST_TAG)
                             .requiredSize(itemSizeDp * 11 + separationDp * 10 * separationSign),
-                    separation = separationDp * separationSign
+                    spacing = separationDp * separationSign
                 ) {
                     Box(Modifier.requiredSize(itemSizeDp))
                 }
@@ -759,7 +759,7 @@
                         Modifier.testTag(TEST_TAG)
                             .requiredSize(pickerHeightDp)
                             .onGloballyPositioned { pickerLayoutCoordinates = it },
-                    separation = separationDp * separationSign,
+                    spacing = separationDp * separationSign,
                     readOnly = readOnly.value,
                     contentDescription = CONTENT_DESCRIPTION,
                 ) { optionIndex ->
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
new file mode 100644
index 0000000..ed1aaf9
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import java.time.LocalTime
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class TimePickerScreenshotTest {
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule val testName = TestName()
+
+    @Test
+    fun timePicker24h_withoutSeconds() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutes24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withSeconds() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesSeconds24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 37)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker12h() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesAmPm12H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withoutSeconds_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutes24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withSeconds_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesSeconds24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 37)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker12h_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesAmPm12H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    private fun ComposeContentTestRule.verifyTimePickerScreenshot(
+        methodName: String,
+        screenshotRule: AndroidXScreenshotTestRule,
+        testTag: String = TEST_TAG,
+        isLargeScreen: Boolean = false,
+        content: @Composable () -> Unit
+    ) {
+        val screenSizeDp = if (isLargeScreen) SCREENSHOT_SIZE_LARGE else SCREENSHOT_SIZE
+        setContentWithTheme {
+            val originalConfiguration = LocalConfiguration.current
+            val fixedScreenSizeConfiguration =
+                remember(originalConfiguration) {
+                    Configuration(originalConfiguration).apply {
+                        screenWidthDp = screenSizeDp
+                        screenHeightDp = screenSizeDp
+                    }
+                }
+            CompositionLocalProvider(LocalConfiguration provides fixedScreenSizeConfiguration) {
+                Box(
+                    modifier =
+                        Modifier.size(screenSizeDp.dp)
+                            .background(MaterialTheme.colorScheme.background)
+                ) {
+                    content()
+                }
+            }
+        }
+
+        onNodeWithTag(testTag).captureToImage().assertAgainstGolden(screenshotRule, methodName)
+    }
+}
+
+private const val SCREENSHOT_SIZE = 192
+private const val SCREENSHOT_SIZE_LARGE = 228
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt
new file mode 100644
index 0000000..9cefdcd
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.content.res.Resources
+import android.os.Build
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.wear.compose.material3.internal.Plurals
+import androidx.wear.compose.material3.internal.Strings
+import androidx.wear.compose.material3.samples.TimePickerSample
+import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import java.time.temporal.ChronoField
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TimePickerTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun timePicker_supports_testtag() {
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                modifier = Modifier.testTag(TEST_TAG),
+                initialTime = LocalTime.now()
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun timePicker_samples_build() {
+        rule.setContentWithTheme {
+            TimePickerSample()
+            TimePickerWithSecondsSample()
+            TimePickerWith12HourClockSample()
+        }
+    }
+
+    @Test
+    fun timePicker_hhmmss_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.second,
+                selectionMode = SelectionMode.Second
+            )
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_hhmm_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutes24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_hhmm12h_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                modifier = Modifier.testTag(TEST_TAG),
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesAmPm12H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.get(ChronoField.CLOCK_HOUR_OF_AMPM),
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+        rule.onNodeWithText("AM", useUnmergedTree = true).assertIsDisplayed()
+        rule.onNodeWithText("PM", useUnmergedTree = true).assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_switch_to_minutes() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        rule.setContentWithTheme { TimePicker(onTimePicked = {}, initialTime = initialTime) }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performClick()
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsFocused()
+    }
+
+    @Test
+    fun timePicker_select_hour() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        val expectedHour = 9
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedHour)
+        rule.waitForIdle()
+
+        rule
+            .onNodeWithTimeValue(selectedValue = expectedHour, selectionMode = SelectionMode.Hour)
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_hhmmss_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11, /* second= */ 59)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.hour)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.second,
+                selectionMode = SelectionMode.Second
+            )
+            .performScrollToIndex(expectedTime.second)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    @Test
+    fun timePicker_hhmm_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutes24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.hour)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    @Test
+    fun timePicker_12h_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesAmPm12H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.get(ChronoField.CLOCK_HOUR_OF_AMPM),
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.get(ChronoField.CLOCK_HOUR_OF_AMPM) - 1)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule.onNodeWithContentDescription("PM").performScrollToIndex(0)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    private fun SemanticsNodeInteractionsProvider.onNodeWithContentDescription(
+        label: String
+    ): SemanticsNodeInteraction = onAllNodesWithContentDescription(label).onFirst()
+
+    private fun SemanticsNodeInteractionsProvider.confirmButton(): SemanticsNodeInteraction =
+        onAllNodesWithContentDescription(
+                InstrumentationRegistry.getInstrumentation()
+                    .context
+                    .resources
+                    .getString(Strings.PickerConfirmButtonContentDescription.value)
+            )
+            .onFirst()
+
+    private fun SemanticsNodeInteractionsProvider.onNodeWithTimeValue(
+        selectedValue: Int,
+        selectionMode: SelectionMode,
+    ): SemanticsNodeInteraction =
+        onAllNodesWithContentDescription(
+                contentDescriptionForValue(
+                    InstrumentationRegistry.getInstrumentation().context.resources,
+                    selectedValue,
+                    selectionMode.contentDescriptionResource
+                )
+            )
+            .onFirst()
+
+    private fun contentDescriptionForValue(
+        resources: Resources,
+        selectedValue: Int,
+        contentDescriptionResource: Plurals,
+    ): String =
+        resources.getQuantityString(contentDescriptionResource.value, selectedValue, selectedValue)
+
+    private enum class SelectionMode(val contentDescriptionResource: Plurals) {
+        Hour(Plurals.TimePickerHoursContentDescription),
+        Minute(Plurals.TimePickerMinutesContentDescription),
+        Second(Plurals.TimePickerSecondsContentDescription),
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
index 9c9d5cb..352df46 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
@@ -33,17 +33,18 @@
 import androidx.compose.ui.test.then
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TestName
 import org.junit.runner.RunWith
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(TestParameterInjector::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class TimeTextScreenshotTest {
     @get:Rule val rule = createComposeRule()
@@ -217,6 +218,28 @@
         }
 
     @Test
+    fun time_text_with_very_long_text_non_round_device() =
+        verifyScreenshot(false) {
+            val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
+            val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
+            val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
+            TimeText(
+                contentColor = Color.Green,
+                timeTextStyle = timeTextStyle,
+                modifier = Modifier.testTag(TEST_TAG),
+                timeSource = MockTimeSource,
+            ) {
+                text(
+                    "Very long text to ensure we are not taking more than one line and " +
+                        "leaving room for the time",
+                    customStyle
+                )
+                separator(separatorStyle)
+                time()
+            }
+        }
+
+    @Test
     fun time_text_with_very_long_text_smaller_angle_on_round_device() =
         verifyScreenshot(true) {
             val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
@@ -238,9 +261,48 @@
             }
         }
 
+    @Test
+    fun time_text_long_text_before_time(@TestParameter shape: ScreenShape) =
+        TimeTextWithDefaults(shape.isRound) {
+            text("Very long text to ensure we are respecting the weight parameter", weight = 1f)
+            separator()
+            time()
+            separator()
+            text("More")
+        }
+
+    @Test
+    fun time_text_long_text_after_time(@TestParameter shape: ScreenShape) =
+        TimeTextWithDefaults(shape.isRound) {
+            text("More")
+            separator()
+            time()
+            separator()
+            text("Very long text to ensure we are respecting the weight parameter", weight = 1f)
+        }
+
+    // This is to get better names, so it says 'round_device' instead of 'true'
+    enum class ScreenShape(val isRound: Boolean) {
+        ROUND_DEVICE(true),
+        SQUARE_DEVICE(false)
+    }
+
+    private fun TimeTextWithDefaults(isDeviceRound: Boolean, content: TimeTextScope.() -> Unit) =
+        verifyScreenshot(isDeviceRound) {
+            TimeText(
+                contentColor = Color.Green,
+                maxSweepAngle = 180f,
+                modifier = Modifier.testTag(TEST_TAG),
+                timeSource = MockTimeSource,
+                content = content
+            )
+        }
+
     private fun verifyScreenshot(isDeviceRound: Boolean = true, content: @Composable () -> Unit) {
         rule.verifyScreenshot(
-            methodName = testName.methodName,
+            // Valid characters for golden identifiers are [A-Za-z0-9_-]
+            // TestParameterInjector adds '[' + parameter_values + ']' to the test name.
+            methodName = testName.methodName.replace("[", "_").replace("]", ""),
             screenshotRule = screenshotRule,
             content = {
                 val screenSize = LocalContext.current.resources.configuration.smallestScreenWidthDp
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt
new file mode 100644
index 0000000..ce54d35
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.dialog
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.SCREENSHOT_GOLDEN_PATH
+import androidx.wear.compose.material3.ScreenSize
+import androidx.wear.compose.material3.TEST_TAG
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.methodNameWithValidCharacters
+import androidx.wear.compose.material3.setContentWithTheme
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(TestParameterInjector::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class AlertDialogScreenshotTest {
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule val testName = TestName()
+
+    @Test
+    fun alert_shortTitle_bottomButton(@TestParameter screenSize: ScreenSize) =
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = false,
+            showText = false,
+            showContent = false,
+            showTwoButtons = false,
+            scrollToBottom = false,
+            screenSize = screenSize,
+            titleText = "Network error"
+        )
+
+    @Test
+    fun alert_shortTitle_confirmDismissButtons(@TestParameter screenSize: ScreenSize) =
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = false,
+            showText = false,
+            showContent = false,
+            showTwoButtons = true,
+            scrollToBottom = false,
+            screenSize = screenSize,
+            titleText = "Network error"
+        )
+
+    @Test
+    fun alert_title_bottomButton(@TestParameter screenSize: ScreenSize) =
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = false,
+            showText = false,
+            showContent = false,
+            showTwoButtons = false,
+            scrollToBottom = false,
+            screenSize = screenSize,
+        )
+
+    @Test
+    fun alert_title_confirmDismissButtons(@TestParameter screenSize: ScreenSize) =
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = false,
+            showText = false,
+            showContent = false,
+            showTwoButtons = true,
+            scrollToBottom = false,
+            screenSize = screenSize
+        )
+
+    @Test
+    fun alert_icon_title_bottomButton(@TestParameter screenSize: ScreenSize) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = false,
+            showContent = false,
+            showTwoButtons = false,
+            scrollToBottom = false,
+            screenSize = screenSize
+        )
+    }
+
+    @Test
+    fun alert_icon_title_confirmDismissButtons(@TestParameter screenSize: ScreenSize) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = false,
+            showContent = false,
+            showTwoButtons = true,
+            scrollToBottom = false,
+            screenSize = screenSize
+        )
+    }
+
+    @Test
+    fun alert_icon_title_messageText_bottomButton(@TestParameter screenSize: ScreenSize) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = true,
+            showContent = false,
+            showTwoButtons = false,
+            scrollToBottom = false,
+            screenSize = screenSize
+        )
+    }
+
+    @Test
+    fun alert_icon_title_messageText_content_confirmDismissButtons(
+        @TestParameter screenSize: ScreenSize
+    ) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = true,
+            showContent = true,
+            showTwoButtons = false,
+            scrollToBottom = false,
+            screenSize = screenSize
+        )
+    }
+
+    @Test
+    fun alert_icon_title_messageText_content_bottomButton_bottom(
+        @TestParameter screenSize: ScreenSize
+    ) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = true,
+            showContent = true,
+            showTwoButtons = false,
+            scrollToBottom = true,
+            screenSize = screenSize
+        )
+    }
+
+    @Test
+    fun alert_icon_title_messageText_content_confirmDismissButtons_bottom(
+        @TestParameter screenSize: ScreenSize
+    ) {
+        rule.verifyAlertDialogScreenshot(
+            testName = testName,
+            screenshotRule = screenshotRule,
+            showIcon = true,
+            showText = true,
+            showContent = true,
+            showTwoButtons = true,
+            scrollToBottom = true,
+            screenSize = screenSize
+        )
+    }
+
+    private fun ComposeContentTestRule.verifyAlertDialogScreenshot(
+        testName: TestName,
+        screenshotRule: AndroidXScreenshotTestRule,
+        showIcon: Boolean,
+        showText: Boolean,
+        showContent: Boolean,
+        showTwoButtons: Boolean,
+        scrollToBottom: Boolean,
+        screenSize: ScreenSize,
+        titleText: String = "Mobile network is not currently available"
+    ) {
+        setContentWithTheme() {
+            val originalConfiguration = LocalConfiguration.current
+            val originalContext = LocalContext.current
+            val fixedScreenSizeConfiguration =
+                remember(originalConfiguration) {
+                    Configuration(originalConfiguration).apply {
+                        screenWidthDp = screenSize.size
+                        screenHeightDp = screenSize.size
+                        screenLayout = Configuration.SCREENLAYOUT_ROUND_YES
+                    }
+                }
+            originalContext.resources.configuration.updateFrom(fixedScreenSizeConfiguration)
+
+            CompositionLocalProvider(
+                LocalContext provides originalContext,
+                LocalConfiguration provides fixedScreenSizeConfiguration,
+            ) {
+                AlertDialogHelper(
+                    modifier = Modifier.size(screenSize.size.dp).testTag(TEST_TAG),
+                    title = { Text(titleText) },
+                    icon =
+                        if (showIcon) {
+                            { Icon(Icons.Filled.Favorite, contentDescription = null) }
+                        } else null,
+                    showTwoButtons = showTwoButtons,
+                    text =
+                        if (showText) {
+                            { Text("Your battery is low. Turn on battery saver.") }
+                        } else null,
+                    content =
+                        if (showContent) {
+                            {
+                                item {
+                                    FilledTonalButton(
+                                        modifier = Modifier.fillMaxWidth(),
+                                        onClick = {},
+                                        label = { Text("Action 1") },
+                                    )
+                                }
+                                item { AlertDialogDefaults.GroupSeparator() }
+                                item {
+                                    FilledTonalButton(
+                                        modifier = Modifier.fillMaxWidth(),
+                                        onClick = {},
+                                        label = { Text("Action 2") },
+                                    )
+                                }
+                            }
+                        } else null
+                )
+            }
+        }
+        if (scrollToBottom) {
+            onNodeWithTag(TEST_TAG).performTouchInput { swipeUp() }
+        }
+        onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, testName.methodNameWithValidCharacters())
+    }
+}
+
+@Composable
+private fun AlertDialogHelper(
+    modifier: Modifier,
+    title: @Composable () -> Unit,
+    icon: @Composable (() -> Unit)?,
+    text: @Composable (() -> Unit)?,
+    showTwoButtons: Boolean,
+    content: (ScalingLazyListScope.() -> Unit)?
+) {
+    if (showTwoButtons) {
+        AlertDialog(
+            show = true,
+            onDismissRequest = {},
+            modifier = modifier,
+            title = title,
+            icon = icon,
+            text = text,
+            confirmButton = { AlertDialogDefaults.ConfirmButton({}) },
+            dismissButton = { AlertDialogDefaults.DismissButton({}) },
+            content = content
+        )
+    } else {
+        AlertDialog(
+            show = true,
+            onDismissRequest = {},
+            modifier = modifier,
+            title = title,
+            icon = icon,
+            text = text,
+            bottomButton = { AlertDialogDefaults.BottomButton({}) },
+            content = content
+        )
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
index 70f79b2..84634d8 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
@@ -251,6 +251,9 @@
 
     // Progress Indicator
     internal var defaultProgressIndicatorColorsCached: ProgressIndicatorColors? = null
+
+    // Picker
+    internal var defaultTimePickerColorsCached: TimePickerColors? = null
 }
 
 /**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
index 05a9703..e5c965c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
@@ -109,8 +109,8 @@
  *   semantics, which facilitates implementation of multi-picker screens.
  * @param scalingParams The parameters to configure the scaling and transparency effects for the
  *   component. See [ScalingParams].
- * @param separation The amount of separation in [Dp] between items. Can be negative, which can be
- *   useful for Text if it has plenty of whitespace.
+ * @param spacing The amount of spacing in [Dp] between items. Can be negative, which can be useful
+ *   for Text if it has plenty of whitespace.
  * @param gradientRatio The size relative to the Picker height that the top and bottom gradients
  *   take. These gradients blur the picker content on the top and bottom. The default is 0.33, so
  *   the top 1/3 and the bottom 1/3 of the picker are taken by gradients. Should be between 0.0 and
@@ -142,7 +142,7 @@
     readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
     onSelected: () -> Unit = {},
     scalingParams: ScalingParams = PickerDefaults.scalingParams(),
-    separation: Dp = 0.dp,
+    spacing: Dp = 0.dp,
     @FloatRange(from = 0.0, to = 0.5) gradientRatio: Float = PickerDefaults.GradientRatio,
     gradientColor: Color = MaterialTheme.colorScheme.background,
     flingBehavior: FlingBehavior = PickerDefaults.flingBehavior(state),
@@ -199,7 +199,7 @@
                                         val shimHeight =
                                             (size.height -
                                                 centerItem.unadjustedSize.toFloat() -
-                                                separation.toPx()) / 2.0f
+                                                spacing.toPx()) / 2.0f
                                         drawShim(gradientColor, shimHeight)
                                     }
                                 }
@@ -229,7 +229,7 @@
             contentPadding = PaddingValues(0.dp),
             scalingParams = scalingParams,
             horizontalAlignment = Alignment.CenterHorizontally,
-            verticalArrangement = Arrangement.spacedBy(space = separation),
+            verticalArrangement = Arrangement.spacedBy(space = spacing),
             flingBehavior = flingBehavior,
             autoCentering = AutoCenteringParams(itemIndex = 0),
             userScrollEnabled = userScrollEnabled
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
index bed5dae..855847c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
@@ -39,6 +39,8 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastMaxOfOrNull
@@ -141,6 +143,7 @@
                     readOnlyLabel = pickerData.readOnlyLabel,
                     flingBehavior = flingBehavior,
                     onSelected = pickerData.onSelected,
+                    spacing = pickerData.spacing,
                     userScrollEnabled = !touchExplorationServicesEnabled || pickerSelected,
                     option = { optionIndex ->
                         with(pickerData) {
@@ -220,6 +223,8 @@
  * @param focusRequester Optional [FocusRequester] for the [Picker]. If not provided, a local
  *   instance of [FocusRequester] will be created to handle the focus between different pickers.
  * @param onSelected Action triggered when the [Picker] is selected by clicking.
+ * @param spacing The amount of spacing in [Dp] between items. Can be negative, which can be useful
+ *   for Text if it has plenty of whitespace.
  * @param readOnlyLabel A slot for providing a label, displayed above the selected option when the
  *   [Picker] is read-only. The label is overlaid with the currently selected option within a Box,
  *   so it is recommended that the label is given [Alignment.TopCenter].
@@ -233,6 +238,7 @@
     val focusRequester: FocusRequester? = null,
     val onSelected: () -> Unit = {},
     val readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
+    val spacing: Dp = 0.dp,
     val option: @Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit
 )
 
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
new file mode 100644
index 0000000..9975891
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -0,0 +1,761 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.collection.IntObjectMap
+import androidx.collection.MutableIntObjectMap
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.focused
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.internal.Plurals
+import androidx.wear.compose.material3.internal.Strings
+import androidx.wear.compose.material3.internal.getPlurals
+import androidx.wear.compose.material3.internal.getString
+import androidx.wear.compose.material3.tokens.TimePickerTokens
+import androidx.wear.compose.materialcore.is24HourFormat
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoField
+
+/**
+ * A full screen TimePicker with configurable columns that allows users to select a time.
+ *
+ * This component is designed to take most/all of the screen and utilizes large fonts.
+ *
+ * Example of a [TimePicker]:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerSample
+ *
+ * Example of a [TimePicker] with seconds:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+ *
+ * Example of a 12 hour clock [TimePicker]:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+ * @param initialTime The initial time to be displayed in the TimePicker.
+ * @param onTimePicked The callback that is called when the user confirms the time selection. It
+ *   provides the selected time as [LocalTime].
+ * @param modifier Modifier to be applied to the `Box` containing the UI elements.
+ * @param timePickerType The different [TimePickerType] supported by this time picker. It indicates
+ *   whether to show seconds or AM/PM selector as well as hours and minutes.
+ * @param colors [TimePickerColors] be applied to the TimePicker.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+fun TimePicker(
+    initialTime: LocalTime,
+    onTimePicked: (LocalTime) -> Unit,
+    modifier: Modifier = Modifier,
+    timePickerType: TimePickerType = TimePickerDefaults.timePickerType,
+    colors: TimePickerColors = TimePickerDefaults.timePickerColors(),
+) {
+    val inspectionMode = LocalInspectionMode.current
+    val fullyDrawn = remember { Animatable(if (inspectionMode) 1f else 0f) }
+
+    val touchExplorationStateProvider = remember { DefaultTouchExplorationStateProvider() }
+    val touchExplorationServicesEnabled by touchExplorationStateProvider.touchExplorationState()
+    // When the time picker loads, none of the individual pickers are selected in talkback mode,
+    // otherwise hours picker should be focused.
+    val pickerGroupState =
+        if (touchExplorationServicesEnabled) {
+            rememberPickerGroupState(FocusableElementsTimePicker.NONE.index)
+        } else {
+            rememberPickerGroupState(FocusableElementsTimePicker.HOURS.index)
+        }
+    val focusRequesterConfirmButton = remember { FocusRequester() }
+
+    val hourString = getString(Strings.TimePickerHour)
+    val minuteString = getString(Strings.TimePickerMinute)
+
+    val is12hour = timePickerType == TimePickerType.HoursMinutesAmPm12H
+    val hourState =
+        if (is12hour) {
+            rememberPickerState(
+                initialNumberOfOptions = 12,
+                initiallySelectedOption = initialTime[ChronoField.CLOCK_HOUR_OF_AMPM] - 1,
+            )
+        } else {
+            rememberPickerState(
+                initialNumberOfOptions = 24,
+                initiallySelectedOption = initialTime.hour,
+            )
+        }
+    val minuteState =
+        rememberPickerState(
+            initialNumberOfOptions = 60,
+            initiallySelectedOption = initialTime.minute,
+        )
+
+    val hoursContentDescription =
+        createDescription(
+            pickerGroupState,
+            if (is12hour) hourState.selectedOption + 1 else hourState.selectedOption,
+            hourString,
+            Plurals.TimePickerHoursContentDescription,
+        )
+    val minutesContentDescription =
+        createDescription(
+            pickerGroupState,
+            minuteState.selectedOption,
+            minuteString,
+            Plurals.TimePickerMinutesContentDescription,
+        )
+
+    val thirdPicker = getOptionalThirdPicker(timePickerType, pickerGroupState, initialTime)
+
+    val onPickerSelected =
+        { current: FocusableElementsTimePicker, next: FocusableElementsTimePicker ->
+            if (pickerGroupState.selectedIndex != current.index) {
+                pickerGroupState.selectedIndex = current.index
+            } else {
+                pickerGroupState.selectedIndex = next.index
+                if (next == FocusableElementsTimePicker.CONFIRM_BUTTON) {
+                    focusRequesterConfirmButton.requestFocus()
+                }
+            }
+        }
+
+    Box(modifier = modifier.fillMaxSize().alpha(fullyDrawn.value)) {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Spacer(Modifier.height(14.dp))
+            val focusedPicker = FocusableElementsTimePicker[pickerGroupState.selectedIndex]
+            FontScaleIndependent {
+                val styles = getTimePickerStyles(timePickerType, thirdPicker)
+                Text(
+                    text =
+                        when {
+                            focusedPicker == FocusableElementsTimePicker.HOURS -> hourString
+                            focusedPicker == FocusableElementsTimePicker.MINUTES -> minuteString
+                            focusedPicker == FocusableElementsTimePicker.SECONDS_OR_PERIOD &&
+                                thirdPicker != null -> thirdPicker.label
+                            else -> ""
+                        },
+                    color = colors.pickerLabelColor,
+                    style = styles.labelTextStyle,
+                    maxLines = 1,
+                    modifier =
+                        Modifier.height(24.dp)
+                            .fillMaxWidth(0.76f)
+                            .align(Alignment.CenterHorizontally),
+                    textAlign = TextAlign.Center
+                )
+                Spacer(Modifier.height(styles.sectionVerticalPadding))
+                Row(
+                    modifier = Modifier.fillMaxWidth().weight(1f),
+                    verticalAlignment = Alignment.CenterVertically,
+                    horizontalArrangement = Arrangement.Center,
+                ) {
+                    val pickerGroupItems =
+                        mutableListOf(
+                            PickerGroupItem(
+                                pickerState = hourState,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.HOURS,
+                                        FocusableElementsTimePicker.MINUTES,
+                                    )
+                                },
+                                contentDescription = hoursContentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        indexToText = {
+                                            "%02d".format(if (is12hour) it + 1 else it)
+                                        },
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                            PickerGroupItem(
+                                pickerState = minuteState,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.MINUTES,
+                                        if (timePickerType == TimePickerType.HoursMinutes24H) {
+                                            FocusableElementsTimePicker.CONFIRM_BUTTON
+                                        } else {
+                                            FocusableElementsTimePicker.SECONDS_OR_PERIOD
+                                        }
+                                    )
+                                },
+                                contentDescription = minutesContentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        indexToText = { "%02d".format(it) },
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                        )
+
+                    if (thirdPicker != null) {
+                        pickerGroupItems.add(
+                            PickerGroupItem(
+                                pickerState = thirdPicker.state,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.SECONDS_OR_PERIOD,
+                                        FocusableElementsTimePicker.CONFIRM_BUTTON,
+                                    )
+                                },
+                                contentDescription = thirdPicker.contentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        indexToText = thirdPicker.indexToText,
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                        )
+                    }
+                    PickerGroup(
+                        *pickerGroupItems.toTypedArray(),
+                        modifier = Modifier.fillMaxWidth(),
+                        pickerGroupState = pickerGroupState,
+                        separator = {
+                            Separator(
+                                textStyle = styles.optionTextStyle,
+                                color = colors.separatorColor,
+                                separatorPadding = styles.separatorPadding,
+                                text = if (it == 0 || !is12hour) ":" else ""
+                            )
+                        },
+                        autoCenter = false,
+                        touchExplorationStateProvider = touchExplorationStateProvider,
+                    )
+                }
+                Spacer(Modifier.height(styles.sectionVerticalPadding))
+            }
+            EdgeButton(
+                onClick = {
+                    val secondOrPeriodSelectedOption = thirdPicker?.state?.selectedOption ?: 0
+                    val confirmedTime =
+                        if (is12hour) {
+                            LocalTime.of(
+                                    hourState.selectedOption + 1,
+                                    minuteState.selectedOption,
+                                    0,
+                                )
+                                .with(
+                                    ChronoField.AMPM_OF_DAY,
+                                    secondOrPeriodSelectedOption.toLong()
+                                )
+                        } else {
+                            LocalTime.of(
+                                hourState.selectedOption,
+                                minuteState.selectedOption,
+                                secondOrPeriodSelectedOption,
+                            )
+                        }
+                    onTimePicked(confirmedTime)
+                },
+                modifier =
+                    Modifier.semantics {
+                            focused =
+                                pickerGroupState.selectedIndex ==
+                                    FocusableElementsTimePicker.CONFIRM_BUTTON.index
+                        }
+                        .focusRequester(focusRequesterConfirmButton)
+                        .focusable(),
+                buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+                colors =
+                    buttonColors(
+                        contentColor = colors.confirmButtonContentColor,
+                        containerColor = colors.confirmButtonContainerColor
+                    ),
+            ) {
+                Icon(
+                    imageVector = Icons.Filled.Check,
+                    contentDescription = getString(Strings.PickerConfirmButtonContentDescription),
+                    modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
+                )
+            }
+        }
+    }
+
+    if (!inspectionMode) {
+        LaunchedEffect(Unit) { fullyDrawn.animateTo(1f) }
+    }
+}
+
+/** Specifies the types of columns to display in the TimePicker. */
+@Immutable
+@JvmInline
+value class TimePickerType internal constructor(internal val value: Int) {
+    companion object {
+        /** Displays two columns for hours (24-hour format) and minutes. */
+        val HoursMinutes24H = TimePickerType(0)
+        /** Displays three columns for hours (24-hour format), minutes and seconds. */
+        val HoursMinutesSeconds24H = TimePickerType(1)
+        /** Displays three columns for hours (12-hour format), minutes and AM/PM label. */
+        val HoursMinutesAmPm12H = TimePickerType(2)
+    }
+
+    override fun toString() =
+        when (this) {
+            HoursMinutes24H -> "HoursMinutes24H"
+            HoursMinutesSeconds24H -> "HoursMinutesSeconds24H"
+            HoursMinutesAmPm12H -> "HoursMinutesAmPm12H"
+            else -> "Unknown"
+        }
+}
+
+/** Contains the default values used by [TimePicker] */
+object TimePickerDefaults {
+
+    /** The default [TimePickerType] for [TimePicker] aligns with the current system time format. */
+    val timePickerType: TimePickerType
+        @Composable
+        get() =
+            if (is24HourFormat()) {
+                TimePickerType.HoursMinutes24H
+            } else {
+                TimePickerType.HoursMinutesAmPm12H
+            }
+
+    /** Creates a [TimePickerColors] for a [TimePicker]. */
+    @Composable fun timePickerColors() = MaterialTheme.colorScheme.defaultTimePickerColors
+
+    /**
+     * Creates a [TimePickerColors] for a [TimePicker].
+     *
+     * @param selectedPickerContentColor The content color of selected picker.
+     * @param unselectedPickerContentColor The content color of unselected pickers.
+     * @param separatorColor The color of separator between the pickers.
+     * @param pickerLabelColor The color of the picker label.
+     * @param confirmButtonContentColor The content color of the confirm button.
+     * @param confirmButtonContainerColor The container color of the confirm button.
+     */
+    @Composable
+    fun timePickerColors(
+        selectedPickerContentColor: Color = Color.Unspecified,
+        unselectedPickerContentColor: Color = Color.Unspecified,
+        separatorColor: Color = Color.Unspecified,
+        pickerLabelColor: Color = Color.Unspecified,
+        confirmButtonContentColor: Color = Color.Unspecified,
+        confirmButtonContainerColor: Color = Color.Unspecified,
+    ) =
+        MaterialTheme.colorScheme.defaultTimePickerColors.copy(
+            selectedPickerContentColor = selectedPickerContentColor,
+            unselectedPickerContentColor = unselectedPickerContentColor,
+            separatorColor = separatorColor,
+            pickerLabelColor = pickerLabelColor,
+            confirmButtonContentColor = confirmButtonContentColor,
+            confirmButtonContainerColor = confirmButtonContainerColor,
+        )
+
+    private val ColorScheme.defaultTimePickerColors: TimePickerColors
+        get() {
+            return defaultTimePickerColorsCached
+                ?: TimePickerColors(
+                        selectedPickerContentColor =
+                            fromToken(TimePickerTokens.SelectedPickerContentColor),
+                        unselectedPickerContentColor =
+                            fromToken(TimePickerTokens.UnselectedPickerContentColor),
+                        separatorColor = fromToken(TimePickerTokens.SeparatorColor),
+                        pickerLabelColor = fromToken(TimePickerTokens.PickerLabelColor),
+                        confirmButtonContentColor =
+                            fromToken(TimePickerTokens.ConfirmButtonContentColor),
+                        confirmButtonContainerColor =
+                            fromToken(TimePickerTokens.ConfirmButtonContainerColor),
+                    )
+                    .also { defaultTimePickerColorsCached = it }
+        }
+}
+
+/**
+ * Represents the colors used by a [TimePicker].
+ *
+ * @param selectedPickerContentColor The content color of selected picker.
+ * @param unselectedPickerContentColor The content color of unselected pickers.
+ * @param separatorColor The color of separator between the pickers.
+ * @param pickerLabelColor The color of the picker label.
+ * @param confirmButtonContentColor The content color of the confirm button.
+ * @param confirmButtonContainerColor The container color of the confirm button.
+ */
+@Immutable
+class TimePickerColors
+constructor(
+    val selectedPickerContentColor: Color,
+    val unselectedPickerContentColor: Color,
+    val separatorColor: Color,
+    val pickerLabelColor: Color,
+    val confirmButtonContentColor: Color,
+    val confirmButtonContainerColor: Color,
+) {
+    internal fun copy(
+        selectedPickerContentColor: Color,
+        unselectedPickerContentColor: Color,
+        separatorColor: Color,
+        pickerLabelColor: Color,
+        confirmButtonContentColor: Color,
+        confirmButtonContainerColor: Color,
+    ) =
+        TimePickerColors(
+            selectedPickerContentColor =
+                selectedPickerContentColor.takeOrElse { this.selectedPickerContentColor },
+            unselectedPickerContentColor =
+                unselectedPickerContentColor.takeOrElse { this.unselectedPickerContentColor },
+            separatorColor = separatorColor.takeOrElse { this.separatorColor },
+            pickerLabelColor = pickerLabelColor.takeOrElse { this.pickerLabelColor },
+            confirmButtonContentColor =
+                confirmButtonContentColor.takeOrElse { this.confirmButtonContentColor },
+            confirmButtonContainerColor =
+                confirmButtonContainerColor.takeOrElse { this.confirmButtonContainerColor },
+        )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || other !is TimePickerColors) return false
+
+        if (selectedPickerContentColor != other.selectedPickerContentColor) return false
+        if (unselectedPickerContentColor != other.unselectedPickerContentColor) return false
+        if (separatorColor != other.separatorColor) return false
+        if (pickerLabelColor != other.pickerLabelColor) return false
+        if (confirmButtonContentColor != other.confirmButtonContentColor) return false
+        if (confirmButtonContainerColor != other.confirmButtonContainerColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = selectedPickerContentColor.hashCode()
+        result = 31 * result + unselectedPickerContentColor.hashCode()
+        result = 31 * result + separatorColor.hashCode()
+        result = 31 * result + pickerLabelColor.hashCode()
+        result = 31 * result + confirmButtonContentColor.hashCode()
+        result = 31 * result + confirmButtonContainerColor.hashCode()
+
+        return result
+    }
+}
+
+@Composable
+private fun getTimePickerStyles(
+    timePickerType: TimePickerType,
+    optionalThirdPicker: PickerData?
+): TimePickerStyles {
+    val isLargeScreen = LocalConfiguration.current.screenWidthDp > 225
+    val labelTextStyle =
+        if (isLargeScreen) {
+                TimePickerTokens.PickerLabelLargeTypography
+            } else {
+                TimePickerTokens.PickerLabelTypography
+            }
+            .value
+
+    val optionTextStyle =
+        if (isLargeScreen || timePickerType == TimePickerType.HoursMinutes24H) {
+                TimePickerTokens.PickerContentLargeTypography
+            } else {
+                TimePickerTokens.PickerContentTypography
+            }
+            .value
+            .copy(textAlign = TextAlign.Center)
+
+    val optionHeight =
+        if (isLargeScreen || timePickerType == TimePickerType.HoursMinutes24H) {
+            40.dp
+        } else {
+            30.dp
+        }
+    val optionSpacing = if (isLargeScreen) 6.dp else 4.dp
+    val separatorPadding =
+        when {
+            timePickerType == TimePickerType.HoursMinutes24H && isLargeScreen -> 12.dp
+            timePickerType == TimePickerType.HoursMinutes24H && !isLargeScreen -> 8.dp
+            timePickerType == TimePickerType.HoursMinutesAmPm12H && isLargeScreen -> 0.dp
+            isLargeScreen -> 6.dp
+            else -> 2.dp
+        }
+
+    val measurer = rememberTextMeasurer()
+    val density = LocalDensity.current
+    val indexToText = optionalThirdPicker?.indexToText ?: { "" }
+
+    val (twoDigitsWidth, textLabelWidth) =
+        remember(
+            density.density,
+            LocalConfiguration.current.screenWidthDp,
+        ) {
+            val mm =
+                measurer.measure(
+                    "0123456789\n${indexToText(0)}\n${indexToText(1)}",
+                    style = optionTextStyle,
+                    density = density,
+                )
+
+            (0..9).maxOf { mm.getBoundingBox(it).width } * 2 to
+                (1..2).maxOf { mm.getLineRight(it) - mm.getLineLeft(it) }
+        }
+    val measuredOptionWidth =
+        with(LocalDensity.current) {
+            if (timePickerType == TimePickerType.HoursMinutesAmPm12H) {
+                max(twoDigitsWidth.toDp(), textLabelWidth.toDp())
+            } else {
+                twoDigitsWidth.toDp()
+            } + 1.dp // Add 1dp buffer to compensate for potential conversion loss
+        }
+
+    return TimePickerStyles(
+        labelTextStyle = labelTextStyle,
+        optionTextStyle = optionTextStyle,
+        optionWidth = max(measuredOptionWidth, minimumInteractiveComponentSize),
+        optionHeight = optionHeight,
+        optionSpacing = optionSpacing,
+        separatorPadding = separatorPadding,
+        sectionVerticalPadding = if (isLargeScreen) 6.dp else 4.dp
+    )
+}
+
+/* Returns the picker data for the third column (AM/PM or seconds) based on the time picker type. */
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+private fun getOptionalThirdPicker(
+    timePickerType: TimePickerType,
+    pickerGroupState: PickerGroupState,
+    time: LocalTime
+): PickerData? =
+    when (timePickerType) {
+        TimePickerType.HoursMinutesSeconds24H -> {
+            val secondString = getString(Strings.TimePickerSecond)
+            val secondState =
+                rememberPickerState(
+                    initialNumberOfOptions = 60,
+                    initiallySelectedOption = time.second,
+                )
+            val secondsContentDescription =
+                createDescription(
+                    pickerGroupState,
+                    secondState.selectedOption,
+                    secondString,
+                    Plurals.TimePickerSecondsContentDescription,
+                )
+            PickerData(
+                state = secondState,
+                contentDescription = secondsContentDescription,
+                label = secondString,
+                indexToText = { "%02d".format(it) }
+            )
+        }
+        TimePickerType.HoursMinutesAmPm12H -> {
+            val periodString = getString(Strings.TimePickerPeriod)
+            val periodState =
+                rememberPickerState(
+                    initialNumberOfOptions = 2,
+                    initiallySelectedOption = time[ChronoField.AMPM_OF_DAY],
+                    repeatItems = false,
+                )
+            val primaryLocale = LocalConfiguration.current.locales[0]
+            val (amString, pmString) =
+                remember(primaryLocale) {
+                    DateTimeFormatter.ofPattern("a", primaryLocale).let { formatter ->
+                        LocalTime.of(0, 0).format(formatter) to
+                            LocalTime.of(12, 0).format(formatter)
+                    }
+                }
+            val periodContentDescription by
+                remember(
+                    pickerGroupState.selectedIndex,
+                    periodState.selectedOption,
+                ) {
+                    derivedStateOf {
+                        if (
+                            pickerGroupState.selectedIndex == FocusableElementsTimePicker.NONE.index
+                        ) {
+                            periodString
+                        } else if (periodState.selectedOption == 0) {
+                            amString
+                        } else {
+                            pmString
+                        }
+                    }
+                }
+            PickerData(
+                state = periodState,
+                contentDescription = periodContentDescription,
+                label = "",
+                indexToText = { if (it == 0) amString else pmString }
+            )
+        }
+        else -> null
+    }
+
+private class PickerData(
+    val state: PickerState,
+    val contentDescription: String,
+    val label: String,
+    val indexToText: (Int) -> String,
+)
+
+private class TimePickerStyles(
+    val labelTextStyle: TextStyle,
+    val optionTextStyle: TextStyle,
+    val optionWidth: Dp,
+    val optionHeight: Dp,
+    val optionSpacing: Dp,
+    val separatorPadding: Dp,
+    val sectionVerticalPadding: Dp,
+)
+
+@Composable
+private fun Separator(
+    textStyle: TextStyle,
+    color: Color,
+    modifier: Modifier = Modifier,
+    separatorPadding: Dp,
+    text: String = ":",
+) {
+    Box(modifier = Modifier.padding(horizontal = separatorPadding)) {
+        Text(
+            text = text,
+            style = textStyle,
+            color = color,
+            modifier = modifier.width(12.dp).clearAndSetSemantics {},
+        )
+    }
+}
+
+private fun pickerTextOption(
+    textStyle: TextStyle,
+    selectedPickerColor: Color,
+    unselectedPickerColor: Color,
+    indexToText: (Int) -> String,
+    optionHeight: Dp,
+): (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) =
+    { value: Int, pickerSelected: Boolean ->
+        Box(
+            modifier = Modifier.fillMaxSize().height(optionHeight),
+            contentAlignment = Alignment.Center
+        ) {
+            Text(
+                text = indexToText(value),
+                maxLines = 1,
+                style = textStyle,
+                color =
+                    if (pickerSelected) {
+                        selectedPickerColor
+                    } else {
+                        unselectedPickerColor
+                    },
+                modifier = Modifier.align(Alignment.Center).wrapContentSize(),
+            )
+        }
+    }
+
+@Composable
+private fun createDescription(
+    pickerGroupState: PickerGroupState,
+    selectedValue: Int,
+    label: String,
+    plurals: Plurals,
+) =
+    when (pickerGroupState.selectedIndex) {
+        FocusableElementsTimePicker.NONE.index -> label
+        else -> getPlurals(plurals, selectedValue, selectedValue)
+    }
+
+@Composable
+private fun FontScaleIndependent(content: @Composable () -> Unit) {
+    CompositionLocalProvider(
+        value =
+            LocalDensity provides
+                Density(
+                    density = LocalDensity.current.density,
+                    fontScale = 1f,
+                ),
+        content = content
+    )
+}
+
+private enum class FocusableElementsTimePicker(val index: Int) {
+    HOURS(0),
+    MINUTES(1),
+    SECONDS_OR_PERIOD(2),
+    CONFIRM_BUTTON(3),
+    NONE(-1),
+    ;
+
+    companion object {
+        private val map: IntObjectMap<FocusableElementsTimePicker> =
+            MutableIntObjectMap<FocusableElementsTimePicker>().apply {
+                values().forEach { put(it.index, it) }
+            }
+
+        operator fun get(value: Int) = map[value]
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
index 2151332..39436f5 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
@@ -125,8 +126,10 @@
                         .sizeIn(maxSweepDegrees = maxSweepAngle)
                         .padding(contentPadding.toArcPadding())
             ) {
-                CurvedTimeTextScope(this, timeText, timeTextStyle, maxSweepAngle, contentColor)
-                    .content()
+                CurvedTimeTextScope(timeText, timeTextStyle, maxSweepAngle, contentColor).apply {
+                    content()
+                    Show()
+                }
             }
         }
     } else {
@@ -151,8 +154,16 @@
      *
      * @param text The text to display.
      * @param style configuration for the [text] such as color, font etc.
+     * @param weight Size the text's width proportional to its weight relative to other weighted
+     *   sibling elements in the TimeText. Specify NaN to make this text not have a weight
+     *   specified. The default value, [TimeTextDefaults.AutoTextWeight], makes this text have
+     *   weight 1f if it's the only one, and not have weight if there are two or more.
      */
-    abstract fun text(text: String, style: TextStyle? = null)
+    abstract fun text(
+        text: String,
+        style: TextStyle? = null,
+        weight: Float = TimeTextDefaults.AutoTextWeight
+    )
 
     /** Adds a text displaying current time. */
     abstract fun time()
@@ -277,6 +288,13 @@
             modifier = CurvedModifier.padding(contentArcPadding)
         )
     }
+
+    /**
+     * Weight value used to specify that the value is automatic. It will be 1f when there is one
+     * text, and no weight will be used if there are 2 or more texts. For the 2+ texts case, usually
+     * one of them should have weight manually specified to ensure its properly cut and ellipsized.
+     */
+    val AutoTextWeight = -1f
 }
 
 interface TimeSource {
@@ -291,46 +309,63 @@
 
 /** Implementation of [TimeTextScope] for round devices. */
 internal class CurvedTimeTextScope(
-    private val scope: CurvedScope,
     private val timeText: String,
     private val timeTextStyle: TextStyle,
     private val maxSweepAngle: Float,
     contentColor: Color,
 ) : TimeTextScope() {
-
+    private var textCount = 0
+    private val pending = mutableListOf<CurvedScope.() -> Unit>()
     private val contentTextStyle = timeTextStyle.merge(contentColor)
 
-    override fun text(text: String, style: TextStyle?) {
-        scope.curvedText(
-            text = text,
-            overflow = TextOverflow.Ellipsis,
-            maxSweepAngle = maxSweepAngle,
-            style = CurvedTextStyle(style = contentTextStyle.merge(style)),
-            modifier = CurvedModifier.weight(1f)
-        )
+    override fun text(text: String, style: TextStyle?, weight: Float) {
+        textCount++
+        pending.add {
+            curvedText(
+                text = text,
+                overflow = TextOverflow.Ellipsis,
+                maxSweepAngle = maxSweepAngle,
+                style = CurvedTextStyle(style = contentTextStyle.merge(style)),
+                modifier =
+                    if (weight.isValidWeight()) CurvedModifier.weight(weight)
+                    // Note that we are creating a lambda here, but textCount is actually read
+                    // later, during the call to Show, when the pending list is fully constructed.
+                    else if (weight == TimeTextDefaults.AutoTextWeight && textCount <= 1)
+                        CurvedModifier.weight(1f)
+                    else CurvedModifier
+            )
+        }
     }
 
     override fun time() {
-        scope.curvedText(
-            timeText,
-            maxSweepAngle = maxSweepAngle,
-            style = CurvedTextStyle(timeTextStyle)
-        )
+        pending.add {
+            curvedText(
+                timeText,
+                maxSweepAngle = maxSweepAngle,
+                style = CurvedTextStyle(timeTextStyle)
+            )
+        }
     }
 
     override fun separator(style: TextStyle?) {
-        scope.CurvedTextSeparator(CurvedTextStyle(style = timeTextStyle.merge(style)))
+        pending.add { CurvedTextSeparator(CurvedTextStyle(style = timeTextStyle.merge(style))) }
     }
 
     override fun composable(content: @Composable () -> Unit) {
-        scope.curvedComposable {
-            CompositionLocalProvider(
-                LocalContentColor provides contentTextStyle.color,
-                LocalTextStyle provides contentTextStyle,
-                content = content
-            )
+        pending.add {
+            curvedComposable {
+                CompositionLocalProvider(
+                    LocalContentColor provides contentTextStyle.color,
+                    LocalTextStyle provides contentTextStyle,
+                    content = content
+                )
+            }
         }
     }
+
+    fun CurvedScope.Show() {
+        pending.fastForEach { it() }
+    }
 }
 
 /** Implementation of [TimeTextScope] for non-round devices. */
@@ -339,11 +374,27 @@
     private val timeTextStyle: TextStyle,
     contentColor: Color,
 ) : TimeTextScope() {
-    private val pending = mutableListOf<@Composable () -> Unit>()
+    private var textCount = 0
+    private val pending = mutableListOf<@Composable RowScope.() -> Unit>()
     private val contentTextStyle = timeTextStyle.merge(contentColor)
 
-    override fun text(text: String, style: TextStyle?) {
-        pending.add { Text(text = text, style = contentTextStyle.merge(style)) }
+    override fun text(text: String, style: TextStyle?, weight: Float) {
+        textCount++
+        pending.add {
+            Text(
+                text = text,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+                style = contentTextStyle.merge(style),
+                modifier =
+                    if (weight.isValidWeight()) Modifier.weight(weight)
+                    // Note that we are creating a lambda here, but textCount is actually read
+                    // later, during the call to Show, when the pending list is fully constructed.
+                    else if (weight == TimeTextDefaults.AutoTextWeight && textCount <= 1)
+                        Modifier.weight(1f)
+                    else Modifier
+            )
+        }
     }
 
     override fun time() {
@@ -365,11 +416,13 @@
     }
 
     @Composable
-    fun Show() {
+    fun RowScope.Show() {
         pending.fastForEach { it() }
     }
 }
 
+private fun Float.isValidWeight() = !isNaN() && this > 0f
+
 internal class DefaultTimeSource(timeFormat: String) : TimeSource {
     private val _timeFormat = timeFormat
 
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
new file mode 100644
index 0000000..4edf6fd
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.internal
+
+import androidx.annotation.PluralsRes
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.wear.compose.material3.R
+
+@Composable
+@ReadOnlyComposable
+internal fun getString(string: Strings): String {
+    return stringResource(string.value)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getString(string: Strings, vararg formatArgs: Any): String {
+    return stringResource(string.value, *formatArgs)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getPlurals(plurals: Plurals, quantity: Int): String {
+    return pluralStringResource(plurals.value, quantity)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getPlurals(plurals: Plurals, quantity: Int, vararg formatArgs: Any): String {
+    return pluralStringResource(plurals.value, quantity, *formatArgs)
+}
+
+@JvmInline
+@Immutable
+internal value class Strings(@StringRes val value: Int) {
+    companion object {
+        inline val TimePickerHour
+            get() = Strings(R.string.wear_m3c_time_picker_hour)
+
+        inline val TimePickerMinute
+            get() = Strings(R.string.wear_m3c_time_picker_minute)
+
+        inline val TimePickerSecond
+            get() = Strings(R.string.wear_m3c_time_picker_second)
+
+        inline val TimePickerPeriod
+            get() = Strings(R.string.wear_m3c_time_picker_period)
+
+        inline val PickerConfirmButtonContentDescription
+            get() = Strings(R.string.wear_m3c_picker_confirm_button_content_description)
+    }
+}
+
+@JvmInline
+@Immutable
+internal value class Plurals(@PluralsRes val value: Int) {
+    companion object {
+        inline val TimePickerHoursContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_hours_content_description)
+
+        inline val TimePickerMinutesContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_minutes_content_description)
+
+        inline val TimePickerSecondsContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_seconds_content_description)
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformBehavior.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformBehavior.kt
new file mode 100644
index 0000000..70458ef
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformBehavior.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.lazy
+
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.lazy.LazyColumnItemScrollProgress
+
+/**
+ * The set of parameters implementing motion transformation behavior for Material3.
+ *
+ * New morphing effect allows to change the shape and the size of the visual element and its extent
+ * depends on the [LazyColumnItemScrollProgress].
+ *
+ * @property morphingMinHeight The minimum height each element morphs to before the whole element
+ *   scales down. Providing null value means no morphing effect will be applied.
+ */
+internal class LazyColumnScrollTransformBehavior(private val morphingMinHeight: () -> Float?) {
+    // Scaling
+
+    /** Scale factor applied to the item at the top edge of the LazyColumn. */
+    private val topEdgeScalingFactor = 0.6f
+
+    /** Scale factor applied to the item at the bottom edge of the LazyColumn. */
+    private val bottomEdgeScaleFactor = 0.3f
+
+    /** Easing applied to the scale factor at the top part of the LazyColumn. */
+    private val topScaleEasing = CubicBezierEasing(0.4f, 0f, 1f, 1f)
+
+    /** Easing applied to the scale factor at the bottom part of the LazyColumn. */
+    private val bottomScaleEasing = CubicBezierEasing(0f, 0f, 0.6f, 1f)
+
+    // Alpha
+
+    /** Alpha applied to the content of the item at the top edge of the LazyColumn. */
+    private val topEdgeContentAlpha = 0.33f
+
+    /** Alpha applied to the content of the item at the bottom edge of the LazyColumn. */
+    private val bottomEdgeContentAlpha = 0f
+
+    /** Alpha easing applied to the content of the item at the bottom edge of the LazyColumn. */
+    private val bottomContentAlphaEasing = CubicBezierEasing(0.6f, 0f, 0.4f, 1f)
+
+    /** Alpha applied to the background of the item at the top edge of the LazyColumn. */
+    private val topEdgeBackgroundAlpha = 0.3f
+
+    /** Alpha applied to the background of the item at the bottom edge of the LazyColumn. */
+    private val bottomEdgeBackgroundAlpha = 0.15f
+
+    // Morphing
+
+    /** Multiplier used to drift the item's bottom edge around sticky bottom line. */
+    private val driftFactor = 0.5f
+
+    /**
+     * Line to which the item's bottom edge will stick, as a percentage of the screen height, while
+     * the rest of the content is morphing.
+     */
+    private val stickyBottomFlippedOffsetPercentage = 0.09f
+
+    /** Final value of the width morphing as percentage of the width. */
+    private val morphWidthTargetPercentage = 0.87f
+
+    private val widthMorphEasing: CubicBezierEasing
+        get() = bottomScaleEasing
+
+    /** Height of an item before scaling is applied. */
+    fun LazyColumnItemScrollProgress.morphedHeight(contentHeight: Float): Float =
+        morphingMinHeight()?.let {
+            val driftingBottomFraction =
+                stickyBottomFlippedOffsetPercentage + (flippedBottomOffsetFraction * driftFactor)
+            if (flippedBottomOffsetFraction < driftingBottomFraction) {
+                val newHeight =
+                    contentHeight * (flippedTopOffsetFraction - driftingBottomFraction) /
+                        (flippedTopOffsetFraction - flippedBottomOffsetFraction)
+                return maxOf(it, newHeight)
+            } else {
+                return@let contentHeight
+            }
+        } ?: contentHeight
+
+    /** Height of an item after all effects are applied. */
+    fun LazyColumnItemScrollProgress.placementHeight(contentHeight: Float): Float =
+        morphedHeight(contentHeight) * scale
+
+    private val LazyColumnItemScrollProgress.flippedTopOffsetFraction: Float
+        get() = 1f - topOffsetFraction
+
+    private val LazyColumnItemScrollProgress.flippedBottomOffsetFraction: Float
+        get() = 1f - bottomOffsetFraction
+
+    val LazyColumnItemScrollProgress.scale: Float
+        get() =
+            when {
+                flippedTopOffsetFraction < 0.5f ->
+                    lerp(
+                        bottomEdgeScaleFactor,
+                        1f,
+                        bottomScaleEasing.transform(
+                            (0f..0.5f).progression(flippedTopOffsetFraction)
+                        )
+                    )
+                flippedBottomOffsetFraction > 0.5f ->
+                    lerp(
+                        1f,
+                        topEdgeScalingFactor,
+                        topScaleEasing.transform(
+                            (0.5f..1f).progression(flippedBottomOffsetFraction)
+                        )
+                    )
+                else -> 1f
+            }
+
+    val LazyColumnItemScrollProgress.backgroundXOffsetFraction: Float
+        get() =
+            when {
+                flippedTopOffsetFraction < 0.3f ->
+                    lerp(
+                        morphWidthTargetPercentage,
+                        1f,
+                        widthMorphEasing.transform((0f..0.3f).progression(flippedTopOffsetFraction))
+                    )
+                else -> 0f
+            }
+
+    val LazyColumnItemScrollProgress.contentXOffsetFraction: Float
+        get() = if (backgroundXOffsetFraction > 0f) 1f - backgroundXOffsetFraction else 0f
+
+    val LazyColumnItemScrollProgress.contentAlpha: Float
+        get() =
+            when {
+                flippedTopOffsetFraction < 0.03f -> 0f
+                flippedTopOffsetFraction < 0.15f ->
+                    lerp(
+                        bottomEdgeContentAlpha,
+                        1f,
+                        bottomContentAlphaEasing.transform(
+                            (0.03f..0.15f).progression(flippedTopOffsetFraction)
+                        )
+                    )
+                flippedBottomOffsetFraction > 0.7f ->
+                    lerp(
+                        1f,
+                        topEdgeContentAlpha,
+                        (0.7f..1f).progression(flippedBottomOffsetFraction)
+                    )
+                else -> 1f
+            }
+
+    val LazyColumnItemScrollProgress.backgroundAlpha: Float
+        get() =
+            when {
+                flippedTopOffsetFraction < 0.3f ->
+                    lerp(
+                        bottomEdgeBackgroundAlpha,
+                        1f,
+                        (0f..0.3f).progression(flippedTopOffsetFraction)
+                    )
+                flippedBottomOffsetFraction > 0.6f ->
+                    lerp(
+                        1f,
+                        topEdgeBackgroundAlpha,
+                        (0.6f..1f).progression(flippedBottomOffsetFraction)
+                    )
+                else -> 1f
+            }
+
+    private fun ClosedRange<Float>.progression(value: Float) =
+        ((value - start) / (endInclusive - start)).coerceIn(0f..1f)
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformModifiers.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformModifiers.kt
new file mode 100644
index 0000000..c386e2c
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/LazyColumnScrollTransformModifiers.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.lazy
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.paint
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.traverseAncestors
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastRoundToInt
+import androidx.wear.compose.foundation.lazy.LazyColumnItemScope
+import androidx.wear.compose.foundation.lazy.LazyColumnItemScrollProgress
+import org.jetbrains.annotations.TestOnly
+
+/**
+ * This modifier provides the height of the target composable to the [scrollTransform] during a
+ * morph transition and represents minimum height of the item when morphed.
+ *
+ * Should be applied to a single child element or none at all (in which case, the morph effect is
+ * disabled). When applied to multiple child elements, the last placed child's height we be used for
+ * morphing.
+ *
+ * @sample androidx.wear.compose.material3.samples.LazyColumnTargetMorphingHeightSample
+ * @param scope The LazyColumnItemScope provides access to the item's index and key.
+ */
+fun Modifier.targetMorphingHeight(
+    @Suppress("UNUSED_PARAMETER") scope: LazyColumnItemScope,
+): Modifier = this then TargetMorphingHeightProviderModifierElement()
+
+/**
+ * A modifier that enables Material3 Motion transformations for content within a LazyColumn item. It
+ * also draws the background behind the content using Material3 Motion transformations. There is
+ * also an overload that applies the same visual transformations to the background.
+ *
+ * This modifier calculates and applies transformations to the content based on the
+ * [LazyColumnItemScrollProgress] of the item inside the LazyColumn. It adjusts the height,
+ * position, applies scaling and morphing effects as the item scrolls.
+ *
+ * @sample androidx.wear.compose.material3.samples.LazyColumnScalingMorphingEffectSample
+ * @param scope The LazyColumnItemScope provides access to the item's index and key.
+ * @param backgroundColor Color of the background.
+ * @param shape Shape of the background.
+ */
+@Composable
+fun Modifier.scrollTransform(
+    scope: LazyColumnItemScope,
+    backgroundColor: Color,
+    shape: Shape = RectangleShape
+): Modifier =
+    with(scope) {
+        var minMorphingHeight by remember { mutableStateOf<Float?>(null) }
+        val spec = remember { LazyColumnScrollTransformBehavior { minMorphingHeight } }
+        val painter =
+            remember(backgroundColor, shape) {
+                ScalingMorphingBackgroundPainter(spec, shape, backgroundColor) { scrollProgress }
+            }
+
+        this@scrollTransform then
+            TargetMorphingHeightConsumerModifierElement { minMorphingHeight = it?.toFloat() } then
+            [email protected](painter) then
+            [email protected] { height, scrollProgress ->
+                with(spec) { scrollProgress.placementHeight(height.toFloat()).fastRoundToInt() }
+            } then
+            [email protected] { contentTransformation(spec) { scrollProgress } }
+    }
+
+/**
+ * A modifier that enables Material3 Motion transformations for content within a LazyColumn item.
+ *
+ * This modifier calculates and applies transformations to the content and background based on the
+ * [LazyColumnItemScrollProgress] of the item inside the LazyColumn. It adjusts the height,
+ * position, applies scaling and morphing effects as the item scrolls.
+ *
+ * @sample androidx.wear.compose.material3.samples.LazyColumnScalingMorphingEffectSample
+ * @param scope The LazyColumnItemScope provides access to the item's index and key.
+ */
+@Composable
+fun Modifier.scrollTransform(
+    scope: LazyColumnItemScope,
+): Modifier =
+    with(scope) {
+        var minMorphingHeight by remember { mutableStateOf<Float?>(null) }
+        val spec = remember { LazyColumnScrollTransformBehavior { minMorphingHeight } }
+
+        this@scrollTransform then
+            TargetMorphingHeightConsumerModifierElement { minMorphingHeight = it?.toFloat() } then
+            [email protected] { height, scrollProgress ->
+                with(spec) { scrollProgress.placementHeight(height.toFloat()).fastRoundToInt() }
+            } then
+            [email protected] { contentTransformation(spec) { scrollProgress } }
+    }
+
+private fun GraphicsLayerScope.contentTransformation(
+    spec: LazyColumnScrollTransformBehavior,
+    scrollProgress: () -> LazyColumnItemScrollProgress?
+) =
+    with(spec) {
+        scrollProgress()?.let {
+            clip = true
+            shape =
+                object : Shape {
+                    override fun createOutline(
+                        size: Size,
+                        layoutDirection: LayoutDirection,
+                        density: Density
+                    ): Outline =
+                        Outline.Rounded(
+                            RoundRect(
+                                rect =
+                                    Rect(
+                                        left = 0f,
+                                        top = 0f,
+                                        right =
+                                            size.width -
+                                                2f * size.width * it.contentXOffsetFraction,
+                                        bottom = it.morphedHeight(size.height)
+                                    ),
+                            )
+                        )
+                }
+            translationX = size.width * it.contentXOffsetFraction * it.scale
+            translationY = -1f * size.height * (1f - it.scale) / 2f
+            alpha = it.contentAlpha.coerceAtMost(0.99f) // Alpha hack.
+            scaleX = it.scale
+            scaleY = it.scale
+        }
+    }
+
+private class ScalingMorphingBackgroundPainter(
+    private val spec: LazyColumnScrollTransformBehavior,
+    private val shape: Shape,
+    private val backgroundColor: Color,
+    private val progress: DrawScope.() -> LazyColumnItemScrollProgress?
+) : Painter() {
+    override val intrinsicSize: Size
+        get() = Size.Unspecified
+
+    override fun DrawScope.onDraw() {
+        with(spec) {
+            progress()?.let {
+                val scale = it.scale
+                val xOffset =
+                    size.width * (1f - scale) / 2f +
+                        it.backgroundXOffsetFraction * size.width * scale
+                val width =
+                    size.width * scale - 2f * it.backgroundXOffsetFraction * size.width * scale
+                translate(xOffset, 0f) {
+                    drawOutline(
+                        outline =
+                            shape.createOutline(
+                                Size(width, it.placementHeight(size.height)),
+                                layoutDirection,
+                                this
+                            ),
+                        color = backgroundColor,
+                        alpha = it.backgroundAlpha,
+                    )
+                }
+            }
+        }
+    }
+}
+
+private const val TargetMorphingHeightTraversalKey = "TargetMorphingHeight"
+
+private class TargetMorphingHeightProviderModifierNode :
+    Modifier.Node(), TraversableNode, LayoutAwareModifierNode {
+    override val traverseKey = TargetMorphingHeightTraversalKey
+
+    private fun reportMinMorphingHeight(height: Int) {
+        traverseAncestors(traverseKey) {
+            if (it is TargetMorphingHeightConsumerModifierNode) {
+                it.onMinMorphingHeightChanged(height)
+                false
+            } else {
+                true
+            }
+        }
+    }
+
+    override fun onPlaced(coordinates: LayoutCoordinates) {
+        reportMinMorphingHeight(coordinates.size.height)
+    }
+
+    override fun onRemeasured(size: IntSize) {
+        reportMinMorphingHeight(size.height)
+    }
+}
+
+private class TargetMorphingHeightProviderModifierElement :
+    ModifierNodeElement<TargetMorphingHeightProviderModifierNode>() {
+    override fun create(): TargetMorphingHeightProviderModifierNode =
+        TargetMorphingHeightProviderModifierNode()
+
+    override fun update(node: TargetMorphingHeightProviderModifierNode) {}
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "TargetMorphingHeightProviderModifierElement"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TargetMorphingHeightConsumerModifierElement) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return 42
+    }
+}
+
+@TestOnly
+internal fun Modifier.minMorphingHeightConsumer(
+    onMinMorphingHeightChanged: (Int?) -> Unit
+): Modifier = this then TargetMorphingHeightConsumerModifierElement(onMinMorphingHeightChanged)
+
+private class TargetMorphingHeightConsumerModifierNode(
+    var onMinMorphingHeightChanged: (Int?) -> Unit
+) : Modifier.Node(), TraversableNode {
+    override val traverseKey = TargetMorphingHeightTraversalKey
+}
+
+private class TargetMorphingHeightConsumerModifierElement(
+    val onMinMorphingHeightChanged: (Int?) -> Unit
+) : ModifierNodeElement<TargetMorphingHeightConsumerModifierNode>() {
+    override fun create() = TargetMorphingHeightConsumerModifierNode(onMinMorphingHeightChanged)
+
+    override fun update(node: TargetMorphingHeightConsumerModifierNode) {
+        node.onMinMorphingHeightChanged = onMinMorphingHeightChanged
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "TargetMorphingHeightConsumerModifierElement"
+        properties["onMinMorphingHeightChanged"] = onMinMorphingHeightChanged
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TargetMorphingHeightConsumerModifierElement) return false
+        return onMinMorphingHeightChanged === other.onMinMorphingHeightChanged
+    }
+
+    override fun hashCode(): Int {
+        return onMinMorphingHeightChanged.hashCode()
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt
new file mode 100644
index 0000000..8700fc6
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.tokens
+
+internal object TimePickerTokens {
+    val SelectedPickerContentColor = ColorSchemeKeyTokens.OnBackground
+    val UnselectedPickerContentColor = ColorSchemeKeyTokens.SecondaryDim
+    val SeparatorColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PickerLabelColor = ColorSchemeKeyTokens.Primary
+    val ConfirmButtonContentColor = ColorSchemeKeyTokens.OnPrimary
+    val ConfirmButtonContainerColor = ColorSchemeKeyTokens.PrimaryDim
+
+    val PickerLabelLargeTypography = TypographyKeyTokens.TitleLarge
+    val PickerLabelTypography = TypographyKeyTokens.TitleMedium
+    val PickerContentLargeTypography = TypographyKeyTokens.NumeralMedium
+    val PickerContentTypography = TypographyKeyTokens.NumeralSmall
+}
diff --git a/wear/compose/compose-material3/src/main/res/values/strings.xml b/wear/compose/compose-material3/src/main/res/values/strings.xml
new file mode 100644
index 0000000..35e6bd7
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string description="Lets the user know that the time unit being changed is hour. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_hour">Hour</string>
+    <string description="Lets the user know that the time unit being changed is minute. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_minute">Minute</string>
+    <string description="Lets the user know that the time unit being changed is second. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_second">Second</string>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_hours_content_description">
+        <item quantity="one">%d Hour</item>
+        <item quantity="other">%d Hours</item>
+    </plurals>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_minutes_content_description">
+        <item quantity="one">%d Minute</item>
+        <item quantity="other">%d Minutes</item>
+    </plurals>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_seconds_content_description">
+        <item quantity="one">%d Second</item>
+        <item quantity="other">%d Seconds</item>
+    </plurals>
+    <string description="Content description of the period picker in TimePickerWith12HourClock. It lets the user select the period for 12H time format. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_period">Period</string>
+    <string description="Content description of the confirm button of DatePicker and TimePicker components. It lets the user confirm the date or time selected. [CHAR_LIMIT=NONE]" name="wear_m3c_picker_confirm_button_content_description">Confirm</string>
+</resources>
\ No newline at end of file
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index 2c17d38..2e021e1 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -31,8 +31,8 @@
 }
 
 dependencies {
-    api("androidx.compose.ui:ui:1.7.0-beta02")
-    api("androidx.compose.runtime:runtime:1.6.0")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
     api("androidx.navigation:navigation-runtime:2.6.0")
     api(project(":wear:compose:compose-material"))
     api("androidx.activity:activity-compose:1.7.0")
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index 9c73539..d49acb1 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -32,7 +32,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.8.1")
-    api("androidx.compose.ui:ui-tooling-preview:1.7.0-beta02")
+    api("androidx.compose.ui:ui-tooling-preview:1.7.0-rc01")
 
     implementation(libs.kotlinStdlib)
     implementation("androidx.wear:wear-tooling-preview:1.0.0")
diff --git a/wear/wear_sdk/README.txt b/wear/wear_sdk/README.txt
index 05609e4..5f080c8 100644
--- a/wear/wear_sdk/README.txt
+++ b/wear/wear_sdk/README.txt
@@ -4,6 +4,6 @@
     "The implementation associated with this version containing are"
     "preinstalled on WearOS devices."
 gerrit source: "vendor/google_clockwork/sdk/lib"
-API version: 35.1
-Build ID: 12205898
-Last updated: Fri Aug  9 05:28:13 PM UTC 2024
+API version: 34.1
+Build ID: 11505490
+Last updated: Wed Feb 28 02:47:27 AM UTC 2024
diff --git a/wear/wear_sdk/wear-sdk.jar b/wear/wear_sdk/wear-sdk.jar
index 36f61d4..16fe240 100644
--- a/wear/wear_sdk/wear-sdk.jar
+++ b/wear/wear_sdk/wear-sdk.jar
Binary files differ