Merge "Move XClassName into a separate file" into androidx-main
diff --git a/.gitignore b/.gitignore
index 9b7bb83..ee07b90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
 !.idea/copyright/AndroidCopyright.xml
 !.idea/copyright/profiles_settings.xml
 !.idea/inspectionProfiles/Project_Default.xml
+!.idea/ktfmt.xml
 .project
 .settings/
 project.properties
diff --git a/.idea/ktfmt.xml b/.idea/ktfmt.xml
new file mode 100644
index 0000000..a2d5a3a
--- /dev/null
+++ b/.idea/ktfmt.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="KtfmtSettings">
+    <option name="enableKtfmt" value="Enabled" />
+    <option name="enabled" value="true" />
+    <option name="uiFormatterStyle" value="Kotlinlang" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
index ede03af..fe26e2c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -26,10 +26,13 @@
 import java.io.File
 import java.nio.file.Files
 import java.nio.file.Paths
+import java.security.MessageDigest
 import javax.inject.Inject
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
 import org.gradle.api.Project
+import org.gradle.api.file.ArchiveOperations
+import org.gradle.api.file.FileSystemOperations
 import org.gradle.api.internal.tasks.userinput.UserInputHandler
 import org.gradle.api.provider.Property
 import org.gradle.api.tasks.Input
@@ -59,6 +62,7 @@
     fun studiow() {
         validateEnvironment()
         install()
+        installKtfmtPlugin()
         launch()
     }
 
@@ -66,8 +70,12 @@
         StudioPlatformUtilities.get(projectRoot, studioInstallationDir)
     }
 
+    @get:Inject abstract val archiveOperations: ArchiveOperations
+
     @get:Inject abstract val execOperations: ExecOperations
 
+    @get:Inject abstract val fileSystemOperations: FileSystemOperations
+
     /**
      * If `true`, checks for `ANDROIDX_PROJECTS` environment variable to decide which projects need
      * to be loaded.
@@ -106,6 +114,29 @@
         File(studioInstallationDir.parentFile, studioArchiveName).absolutePath
     }
 
+    /** Directory where Studio downloads plugins to */
+    private val studioPluginDir =
+        File(System.getenv("HOME"), ".AndroidStudioAndroidX/config/plugins").also { it.mkdirs() }
+
+    private val studioKtfmtPluginVersion by lazy { project.getVersionByName("ktfmtIdeaPlugin") }
+
+    /**
+     * This ID changes for each ktfmt plugin version; see
+     * https://plugins.jetbrains.com/plugin/14912-ktfmt/versions/stable and you'll see the number in
+     * the redirection URL when hovering over the [studioKtfmtPluginVersion] you want downloaded
+     */
+    private val studioKtfmtPluginId = "553364"
+
+    private val studioKtfmtPluginDownloadUrl =
+        "https://downloads.marketplace.jetbrains.com/files/14912/$studioKtfmtPluginId/ktfmt_idea_plugin-$studioKtfmtPluginVersion.zip"
+
+    /** Storage location for the ktfmt plugin zip file */
+    private val studioKtfmtPluginZip = File(studioPluginDir, "ktfmt-$studioKtfmtPluginVersion.zip")
+
+    /** Download ktfmt plugin zip file and run `shasum -a 256 ./path/to/zip` to get checksum */
+    private val studioKtfmtPluginChecksum =
+        "79602c7fa94a23df7ca5c06effd50b180bc6518396488e20662f8d5d52b323db"
+
     /** The idea.properties file that we want to tell Studio to use */
     @get:Internal protected abstract val ideaProperties: File
 
@@ -159,6 +190,40 @@
         }
     }
 
+    private fun installKtfmtPlugin() {
+        // TODO: When upgrading to ktfmt_idea_plugin 1.2.x.x, remove the `instrumented-` prefix from
+        // the plugin jar name
+        if (
+            File(
+                    studioPluginDir,
+                    "ktfmt_idea_plugin/lib/instrumented-ktfmt_idea_plugin-$studioKtfmtPluginVersion.jar"
+                )
+                .exists()
+        ) {
+            return
+        } else {
+            File(studioPluginDir, "ktfmt_idea_plugin").deleteRecursively()
+        }
+
+        println("Downloading ktfmt plugin from $studioKtfmtPluginDownloadUrl")
+        execOperations.exec { execSpec ->
+            with(execSpec) {
+                executable("curl")
+                args(studioKtfmtPluginDownloadUrl, "--output", studioKtfmtPluginZip.absolutePath)
+            }
+        }
+
+        studioKtfmtPluginZip.verifyChecksum()
+
+        println("Installing ktfmt plugin into ${studioPluginDir.absolutePath}")
+        fileSystemOperations.copy {
+            it.from(archiveOperations.zipTree(studioKtfmtPluginZip))
+            it.into(studioPluginDir)
+        }
+        studioKtfmtPluginZip.delete()
+        println("ktfmt plugin installed successfully.")
+    }
+
     /** Attempts to symlink the system-images and emulator SDK directories to a canonical SDK. */
     private fun setupSymlinksIfNeeded() {
         val paths = listOf("system-images", "emulator")
@@ -177,7 +242,7 @@
                 }
             }
 
-        val canonicalSdkPath = File(File(System.getProperty("user.home")).parent, relativeSdkPath)
+        val canonicalSdkPath = File(System.getenv("HOME"), relativeSdkPath)
         if (!canonicalSdkPath.exists()) {
             // In the future, we might want to try a little harder to locate a canonical SDK path.
             println("Failed to locate canonical SDK, not found at: $canonicalSdkPath")
@@ -333,6 +398,26 @@
         File(studioArchivePath).delete()
     }
 
+    private fun File.verifyChecksum() {
+        val actualChecksum =
+            MessageDigest.getInstance("SHA-256")
+                .also { it.update(this.readBytes()) }
+                .digest()
+                .joinToString(separator = "") { "%02x".format(it) }
+
+        if (actualChecksum != studioKtfmtPluginChecksum) {
+            this.delete()
+            throw GradleException(
+                """
+                Checksum mismatch for file: ${this.absolutePath}
+                Expected: $studioKtfmtPluginChecksum
+                Actual:   $actualChecksum
+                """
+                    .trimIndent()
+            )
+        }
+    }
+
     companion object {
         private const val STUDIO_TASK = "studio"
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
index 6b6d2ef..759e3cf 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
@@ -19,11 +19,15 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraMetadata
 import android.os.Build
+import android.util.Size
 import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.CameraMode
+import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.SurfaceConfig.ConfigSize
 import androidx.camera.core.impl.SurfaceConfig.ConfigType
+import androidx.camera.core.impl.SurfaceSizeDefinition
 
 public object GuaranteedConfigurationsUtil {
     @JvmStatic
@@ -902,4 +906,44 @@
             .also { combinationList.add(it) }
         return combinationList
     }
+
+    /** Returns the supported stream combinations for high-speed sessions. */
+    @JvmStatic
+    public fun generateHighSpeedSupportedCombinationList(
+        maxSupportedSize: Size,
+        surfaceSizeDefinition: SurfaceSizeDefinition
+    ): List<SurfaceCombination> {
+        val surfaceCombinations = mutableListOf<SurfaceCombination>()
+
+        // Find the closest SurfaceConfig that can contain the max supported size. Ultimately,
+        // the target resolution still needs to be verified by the StreamConfigurationMap API for
+        // high-speed.
+        val surfaceConfig =
+            SurfaceConfig.transformSurfaceConfig(
+                CameraMode.DEFAULT,
+                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                maxSupportedSize,
+                surfaceSizeDefinition
+            )
+
+        // Create high-speed supported combinations based on the constraints:
+        // - Only support preview and/or video surface.
+        // - Maximum 2 surfaces.
+        // - All surfaces must have the same size.
+
+        // PRIV
+        SurfaceCombination()
+            .apply { addSurfaceConfig(surfaceConfig) }
+            .also { surfaceCombinations.add(it) }
+
+        // PRIV + PRIV
+        SurfaceCombination()
+            .apply {
+                addSurfaceConfig(surfaceConfig)
+                addSurfaceConfig(surfaceConfig)
+            }
+            .also { surfaceCombinations.add(it) }
+
+        return surfaceCombinations
+    }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
index 0c7d723..185aaed 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
@@ -39,6 +39,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.TargetAspectRatio
 import androidx.camera.camera2.pipe.integration.impl.DisplayInfoManager
 import androidx.camera.camera2.pipe.integration.internal.DynamicRangeResolver
+import androidx.camera.camera2.pipe.integration.internal.HighSpeedResolver
 import androidx.camera.camera2.pipe.integration.internal.StreamUseCaseUtil
 import androidx.camera.core.DynamicRange
 import androidx.camera.core.impl.AttachedSurfaceInfo
@@ -93,6 +94,7 @@
     private val ultraHighSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
     private val previewStabilizationSurfaceCombinations: MutableList<SurfaceCombination> =
         mutableListOf()
+    private val highSpeedSurfaceCombinations = mutableListOf<SurfaceCombination>()
     private val featureSettingsToSupportedCombinationsMap:
         MutableMap<FeatureSettings, List<SurfaceCombination>> =
         mutableMapOf()
@@ -113,6 +115,7 @@
     private val resolutionCorrector = ResolutionCorrector()
     private val targetAspectRatio: TargetAspectRatio = TargetAspectRatio()
     private val dynamicRangeResolver: DynamicRangeResolver = DynamicRangeResolver(cameraMetadata)
+    private val highSpeedResolver: HighSpeedResolver = HighSpeedResolver(cameraMetadata)
 
     init {
         checkCapabilities()
@@ -192,6 +195,11 @@
             if (featureSettings.cameraMode == CameraMode.DEFAULT) {
                 supportedSurfaceCombinations.addAll(surfaceCombinationsUltraHdr)
             }
+        } else if (featureSettings.isHighSpeedOn) {
+            if (highSpeedSurfaceCombinations.isEmpty()) {
+                generateHighSpeedSupportedCombinationList()
+            }
+            supportedSurfaceCombinations.addAll(highSpeedSurfaceCombinations)
         } else if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_8_BIT) {
             when (featureSettings.cameraMode) {
                 CameraMode.CONCURRENT_CAMERA ->
@@ -258,12 +266,23 @@
         attachedSurfaces: List<AttachedSurfaceInfo>,
         newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
         isPreviewStabilizationOn: Boolean = false,
-        hasVideoCapture: Boolean = false
+        hasVideoCapture: Boolean = false,
+        targetHighSpeedFpsRange: Range<Int>? = null
     ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
         // Refresh Preview Size based on current display configurations.
         refreshPreviewSize()
 
-        val newUseCaseConfigs = newUseCaseConfigsSupportedSizeMap.keys.toList()
+        val isHighSpeedOn = targetHighSpeedFpsRange != null
+        // Filter out unsupported sizes for high-speed at the beginning to ensure correct
+        // resolution selection later. High-speed session requires all surface sizes to be the same.
+        val filteredNewUseCaseConfigsSupportedSizeMap =
+            if (isHighSpeedOn) {
+                highSpeedResolver.filterCommonSupportedSizes(newUseCaseConfigsSupportedSizeMap)
+            } else {
+                newUseCaseConfigsSupportedSizeMap
+            }
+
+        val newUseCaseConfigs = filteredNewUseCaseConfigsSupportedSizeMap.keys.toList()
 
         // Get the index order list by the use case priority for finding stream configuration
         val useCasesPriorityOrder = getUseCasesPriorityOrder(newUseCaseConfigs)
@@ -273,19 +292,20 @@
                 newUseCaseConfigs,
                 useCasesPriorityOrder
             )
-        val isUltraHdrOn = isUltraHdrOn(attachedSurfaces, newUseCaseConfigsSupportedSizeMap)
+        val isUltraHdrOn = isUltraHdrOn(attachedSurfaces, filteredNewUseCaseConfigsSupportedSizeMap)
         val featureSettings =
             createFeatureSettings(
                 cameraMode,
                 resolvedDynamicRanges,
                 isPreviewStabilizationOn,
-                isUltraHdrOn
+                isUltraHdrOn,
+                isHighSpeedOn
             )
         val isSurfaceCombinationSupported =
             isUseCasesCombinationSupported(
                 featureSettings,
                 attachedSurfaces,
-                newUseCaseConfigsSupportedSizeMap
+                filteredNewUseCaseConfigsSupportedSizeMap
             )
         require(isSurfaceCombinationSupported) {
             "No supported surface combination is found for camera device - Id : $cameraId. " +
@@ -295,11 +315,22 @@
 
         // Calculates the target FPS range
         val targetFpsRange =
-            getTargetFpsRange(attachedSurfaces, newUseCaseConfigs, useCasesPriorityOrder)
+            if (isHighSpeedOn) targetHighSpeedFpsRange
+            else getTargetFpsRange(attachedSurfaces, newUseCaseConfigs, useCasesPriorityOrder)
         // Filters the unnecessary output sizes for performance improvement. This will
         // significantly reduce the number of all possible size arrangements below.
         val useCaseConfigToFilteredSupportedSizesMap =
-            filterSupportedSizes(newUseCaseConfigsSupportedSizeMap, featureSettings, targetFpsRange)
+            filterSupportedSizes(
+                filteredNewUseCaseConfigsSupportedSizeMap,
+                featureSettings,
+                targetFpsRange
+            )
+        val supportedOutputSizesList =
+            getSupportedOutputSizesList(
+                useCaseConfigToFilteredSupportedSizesMap,
+                newUseCaseConfigs,
+                useCasesPriorityOrder
+            )
         // The two maps are used to keep track of the attachedSurfaceInfo or useCaseConfigs the
         // surfaceConfigs are made from. They are populated in getSurfaceConfigListAndFpsCeiling().
         // The keys are the position of their corresponding surfaceConfigs in the list. We can
@@ -310,14 +341,8 @@
             mutableMapOf()
         val surfaceConfigIndexUseCaseConfigMap: MutableMap<Int, UseCaseConfig<*>> = mutableMapOf()
         val allPossibleSizeArrangements =
-            getAllPossibleSizeArrangements(
-                getSupportedOutputSizesList(
-                    useCaseConfigToFilteredSupportedSizesMap,
-                    newUseCaseConfigs,
-                    useCasesPriorityOrder
-                )
-            )
-
+            if (isHighSpeedOn) highSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+            else getAllPossibleSizeArrangements(supportedOutputSizesList)
         val containsZsl: Boolean =
             StreamUseCaseUtil.containsZslUseCase(attachedSurfaces, newUseCaseConfigs)
         var orderedSurfaceConfigListForStreamUseCase: List<SurfaceConfig>? = null
@@ -336,7 +361,8 @@
                 )
         }
 
-        val maxSupportedFps = getMaxSupportedFpsFromAttachedSurfaces(attachedSurfaces)
+        val maxSupportedFps =
+            getMaxSupportedFpsFromAttachedSurfaces(attachedSurfaces, isHighSpeedOn)
         val bestSizesAndFps =
             findBestSizesAndFps(
                 allPossibleSizeArrangements,
@@ -356,7 +382,8 @@
                 newUseCaseConfigs,
                 useCasesPriorityOrder,
                 resolvedDynamicRanges,
-                hasVideoCapture
+                hasVideoCapture,
+                isHighSpeedOn
             )
         val attachedSurfaceStreamSpecMap = mutableMapOf<AttachedSurfaceInfo, StreamSpec>()
 
@@ -385,7 +412,8 @@
         @CameraMode.Mode cameraMode: Int,
         resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>,
         isPreviewStabilizationOn: Boolean,
-        isUltraHdrOn: Boolean
+        isUltraHdrOn: Boolean,
+        isHighSpeedOn: Boolean
     ): FeatureSettings {
         require(!(cameraMode != CameraMode.DEFAULT && isUltraHdrOn)) {
             "Camera device Id is $cameraId. Ultra HDR is not " +
@@ -400,11 +428,17 @@
             "Camera device Id is $cameraId. 10 bit dynamic range is not " +
                 "currently supported in ${CameraMode.toLabelString(cameraMode)} camera mode."
         }
+
+        require(!(isHighSpeedOn && !highSpeedResolver.isHighSpeedSupported)) {
+            "High-speed session is not supported on this device."
+        }
+
         return FeatureSettings(
             cameraMode,
             requiredMaxBitDepth,
             isPreviewStabilizationOn,
-            isUltraHdrOn
+            isUltraHdrOn,
+            isHighSpeedOn
         )
     }
 
@@ -468,7 +502,7 @@
      * surfaceConfigIndexAttachedSurfaceInfoMap and surfaceConfigIndexUseCaseConfigMap.
      */
     private fun getOrderedSurfaceConfigListForStreamUseCase(
-        allPossibleSizeArrangements: List<MutableList<Size>>,
+        allPossibleSizeArrangements: List<List<Size>>,
         attachedSurfaces: List<AttachedSurfaceInfo>,
         newUseCaseConfigs: List<UseCaseConfig<*>>,
         useCasesPriorityOrder: List<Int>,
@@ -625,6 +659,7 @@
 
     private fun getMaxSupportedFpsFromAttachedSurfaces(
         attachedSurfaces: List<AttachedSurfaceInfo>,
+        isHighSpeedOn: Boolean
     ): Int {
         var existingSurfaceFrameRateCeiling = Int.MAX_VALUE
         for (attachedSurfaceInfo in attachedSurfaces) {
@@ -633,7 +668,8 @@
                 getUpdatedMaximumFps(
                     existingSurfaceFrameRateCeiling,
                     attachedSurfaceInfo.imageFormat,
-                    attachedSurfaceInfo.size
+                    attachedSurfaceInfo.size,
+                    isHighSpeedOn
                 )
         }
         return existingSurfaceFrameRateCeiling
@@ -668,7 +704,7 @@
                 // Filters the sizes with frame rate only if there is target FPS setting
                 val maxFrameRate =
                     if (targetFpsRange != null) {
-                        getMaxFrameRate(imageFormat, size)
+                        getMaxFrameRate(imageFormat, size, featureSettings.isHighSpeedOn)
                     } else {
                         Int.MAX_VALUE
                     }
@@ -716,7 +752,7 @@
     }
 
     private fun findBestSizesAndFps(
-        allPossibleSizeArrangements: List<MutableList<Size>>,
+        allPossibleSizeArrangements: List<List<Size>>,
         attachedSurfaces: List<AttachedSurfaceInfo>,
         newUseCaseConfigs: List<UseCaseConfig<*>>,
         existingSurfaceFrameRateCeiling: Int,
@@ -750,7 +786,8 @@
                     possibleSizeList,
                     newUseCaseConfigs,
                     useCasesPriorityOrder,
-                    existingSurfaceFrameRateCeiling
+                    existingSurfaceFrameRateCeiling,
+                    featureSettings.isHighSpeedOn
                 )
             var isConfigFrameRateAcceptable = true
             if (targetFrameRateForConfig != null) {
@@ -841,13 +878,25 @@
         newUseCaseConfigs: List<UseCaseConfig<*>>,
         useCasesPriorityOrder: List<Int>,
         resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>,
-        hasVideoCapture: Boolean
+        hasVideoCapture: Boolean,
+        isHighSpeedOn: Boolean
     ): MutableMap<UseCaseConfig<*>, StreamSpec> {
         val suggestedStreamSpecMap = mutableMapOf<UseCaseConfig<*>, StreamSpec>()
         var targetFrameRateForDevice: Range<Int>? = null
         if (targetFpsRange != null) {
+            // get all fps ranges supported by device
+            val availableFpsRanges =
+                if (isHighSpeedOn) {
+                    highSpeedResolver.getFrameRateRangesFor(bestSizesAndMaxFps.bestSizes)
+                } else {
+                    cameraMetadata[CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES]
+                }
             targetFrameRateForDevice =
-                getClosestSupportedDeviceFrameRate(targetFpsRange, bestSizesAndMaxFps.maxFps)
+                getClosestSupportedDeviceFrameRate(
+                    targetFpsRange,
+                    bestSizesAndMaxFps.maxFps,
+                    availableFpsRanges
+                )
         }
         for ((index, useCaseConfig) in newUseCaseConfigs.withIndex()) {
             val resolutionForUseCase =
@@ -922,6 +971,7 @@
         newUseCaseConfigs: List<UseCaseConfig<*>>,
         useCasesPriorityOrder: List<Int>,
         currentConfigFrameRateCeiling: Int,
+        isHighSpeedOn: Boolean
     ): Int {
         var newConfigFrameRateCeiling: Int = currentConfigFrameRateCeiling
         // Attach SurfaceConfig of new use cases
@@ -930,11 +980,24 @@
             // get the maximum fps of the new surface and update the maximum fps of the
             // proposed configuration
             newConfigFrameRateCeiling =
-                getUpdatedMaximumFps(newConfigFrameRateCeiling, newUseCase.inputFormat, size)
+                getUpdatedMaximumFps(
+                    newConfigFrameRateCeiling,
+                    newUseCase.inputFormat,
+                    size,
+                    isHighSpeedOn
+                )
         }
         return newConfigFrameRateCeiling
     }
 
+    private fun getMaxFrameRate(imageFormat: Int, size: Size, isHighSpeedOn: Boolean): Int {
+        return if (isHighSpeedOn) {
+            highSpeedResolver.getMaxFrameRate(imageFormat, size)
+        } else {
+            getMaxFrameRate(imageFormat, size)
+        }
+    }
+
     private fun getMaxFrameRate(imageFormat: Int, size: Size?): Int {
         var maxFrameRate = 0
         try {
@@ -1019,23 +1082,23 @@
      *
      * @param targetFrameRate The Target Frame Rate resolved from all current existing surfaces and
      *   incoming new use cases.
+     * @param availableFpsRanges the device available frame rate ranges.
      * @return A frame rate range supported by the device that is closest to targetFrameRate when it
      *   is specified. [StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED] is returned if targetFrameRate is
      *   [StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED].
      */
     private fun getClosestSupportedDeviceFrameRate(
         targetFrameRate: Range<Int>,
-        maxFps: Int
+        maxFps: Int,
+        availableFpsRanges: Array<out Range<Int>>?
     ): Range<Int> {
         if (targetFrameRate == StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED) {
             return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
         }
 
+        availableFpsRanges ?: return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+
         var newTargetFrameRate = targetFrameRate
-        // get all fps ranges supported by device
-        val availableFpsRanges =
-            cameraMetadata[CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES]
-                ?: return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
         // if  whole target frame rate range > maxFps of configuration, the target for this
         // calculation will be [max,max].
 
@@ -1135,9 +1198,15 @@
      * @param currentMaxFps the previously stored Max FPS
      * @param imageFormat the image format of the incoming surface
      * @param size the size of the incoming surface
+     * @param isHighSpeedOn whether high-speed session is enabled
      */
-    private fun getUpdatedMaximumFps(currentMaxFps: Int, imageFormat: Int, size: Size): Int {
-        return min(currentMaxFps, getMaxFrameRate(imageFormat, size))
+    private fun getUpdatedMaximumFps(
+        currentMaxFps: Int,
+        imageFormat: Int,
+        size: Size,
+        isHighSpeedOn: Boolean
+    ): Int {
+        return min(currentMaxFps, getMaxFrameRate(imageFormat, size, isHighSpeedOn))
     }
 
     /**
@@ -1276,6 +1345,24 @@
         )
     }
 
+    private fun generateHighSpeedSupportedCombinationList() {
+        if (!highSpeedResolver.isHighSpeedSupported) {
+            return
+        }
+        highSpeedSurfaceCombinations.clear()
+        // Find maximum supported size.
+        highSpeedResolver.maxSize?.let { maxSize ->
+            highSpeedSurfaceCombinations.addAll(
+                GuaranteedConfigurationsUtil.generateHighSpeedSupportedCombinationList(
+                    maxSize,
+                    getUpdatedSurfaceSizeDefinitionByFormat(
+                        ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+                    )
+                )
+            )
+        }
+    }
+
     private fun generate10BitSupportedCombinationList() {
         surfaceCombinations10Bit.addAll(
             GuaranteedConfigurationsUtil.get10BitSupportedCombinationList()
@@ -1616,7 +1703,8 @@
         @CameraMode.Mode val cameraMode: Int,
         val requiredMaxBitDepth: Int,
         val isPreviewStabilizationOn: Boolean = false,
-        val isUltraHdrOn: Boolean = false
+        val isUltraHdrOn: Boolean = false,
+        val isHighSpeedOn: Boolean = false
     )
 
     public data class BestSizesAndMaxFpsForConfigs(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index cca39a8..96b5f9c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -412,7 +412,7 @@
                             it.lock3AForCapture(
                                     timeLimitNs = timeLimitNs,
                                     triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
-                                    waitForAwb = true,
+                                    waitForAwb = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
                                 )
                                 .await()
                         }
@@ -489,7 +489,8 @@
                     debug { "CapturePipeline#aePreCaptureApplyCapture: Locking 3A for capture" }
                     it.lock3AForCapture(
                             timeLimitNs = timeLimitNs,
-                            triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY
+                            triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
+                            waitForAwb = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
                         )
                         .join()
                     debug {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolver.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolver.kt
new file mode 100644
index 0000000..8317a94
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolver.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.graphics.ImageFormat.PRIVATE
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
+import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+import android.os.Build
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.core.Logger
+import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+import androidx.camera.core.internal.utils.SizeUtil.getArea
+
+/** A class responsible for resolving parameters for high-speed session scenario. */
+public class HighSpeedResolver(private val cameraMetadata: CameraMetadata) {
+
+    /** Indicates whether the camera supports high-speed session. */
+    public val isHighSpeedSupported: Boolean by lazy {
+        Build.VERSION.SDK_INT >= 23 &&
+            cameraMetadata[REQUEST_AVAILABLE_CAPABILITIES]?.any {
+                it == REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+            } == true
+    }
+
+    /** The maximum supported size based on area, or `null` if there are no supported sizes. */
+    public val maxSize: Size? by lazy {
+        supportedSizes.takeIf { it.isNotEmpty() }?.maxBy { getArea(it) }
+    }
+
+    private val streamConfigurationMapCompat: StreamConfigurationMapCompat by lazy {
+        val map =
+            cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+                ?: throw IllegalArgumentException("Cannot retrieve SCALER_STREAM_CONFIGURATION_MAP")
+        StreamConfigurationMapCompat(map, OutputSizesCorrector(cameraMetadata, map))
+    }
+
+    private val supportedSizes: List<Size> by lazy {
+        streamConfigurationMapCompat.getHighSpeedVideoSizes()?.toList() ?: emptyList()
+    }
+
+    /**
+     * Filters supported sizes for each use case, retaining only the sizes common to all use cases
+     * and present in the overall supported sizes.
+     *
+     * This function analyzes a map of use case configurations and their corresponding lists of
+     * supported sizes. It identifies the sizes common to all use cases and filters each use case's
+     * supported sizes, retaining only those that are both common across all use cases and present
+     * in the `supportedSizes` list. The original order of the supported sizes for each use case is
+     * preserved.
+     *
+     * @param sizesMap A map where keys represent use case configurations and values are lists of
+     *   `Size` objects representing the supported sizes for each use case.
+     * @return A new map with the same keys as the input `sizesMap`, but with the values (lists of
+     *   sizes) filtered to contain only the common supported sizes that are also present in the
+     *   `supportedSizes` list, while maintaining the original order.
+     */
+    public fun <T> filterCommonSupportedSizes(sizesMap: Map<T, List<Size>>): Map<T, List<Size>> {
+        val commonSupportedSizes =
+            sizesMap.values.toList().findCommonElements().filter { it in supportedSizes }
+        return sizesMap.mapValues { (_, sizes) -> sizes.filter { it in commonSupportedSizes } }
+    }
+
+    /**
+     * Returns the maximum frame rate supported for a given size in a high-speed session.
+     *
+     * This method retrieves the supported high-speed FPS ranges for the given size from the camera
+     * characteristics. It then returns the maximum frame rate (upper bound) among those ranges.
+     *
+     * @param imageFormat The image format. Only [PRIVATE] is supported for high-speed session.
+     * @param size The size for which to find the maximum supported high-speed frame rate.
+     * @return The maximum high-speed frame rate supported for the given size, or 0 if no high-speed
+     *   FPS ranges are supported for that size or the image format is not supported.
+     */
+    public fun getMaxFrameRate(imageFormat: Int, size: Size): Int {
+        if (imageFormat != SUPPORTED_FORMAT) {
+            return 0
+        }
+
+        val supportedFpsRangesForSize =
+            getHighSpeedVideoFpsRangesFor(size).takeIf { it.isNotEmpty() }
+                ?: run {
+                    Logger.w(TAG, "No supported high speed  fps for $size")
+                    return 0
+                }
+
+        return supportedFpsRangesForSize.maxOf { it.upper }
+    }
+
+    /**
+     * Returns size arrangements where all inner lists have the same size, maintaining order.
+     *
+     * This method takes a list of lists of sizes, where each inner list represents the supported
+     * sizes for a specific use case. It finds the common sizes across all use cases and creates
+     * arrangements where each use case has the same size. The order in the first list of the input
+     * determines the order of the common sizes in the output.
+     *
+     * This method is necessary due to a limitation in high-speed session configuration, where all
+     * streams (use cases) in a high-speed session must have the same size.
+     *
+     * @param sizesList A list of lists of sizes. Each inner list represents the supported sizes for
+     *   a use case. The first dimension represents the use case, and the second dimension is the
+     *   supported sizes.
+     * @return A list of size arrangements where each inner list contains the same size. Returns an
+     *   empty list if the input is empty or null.
+     */
+    public fun getSizeArrangements(sizesList: List<List<Size>>): List<List<Size>> {
+        if (sizesList.isEmpty()) {
+            return emptyList()
+        }
+
+        val commonSizes = sizesList.findCommonElements()
+
+        // Generate arrangements with common sizes.
+        return commonSizes.map { commonSize -> List(sizesList.size) { commonSize } }
+    }
+
+    /**
+     * Returns the supported frame rate ranges for high-speed capture sessions with the given
+     * surface sizes.
+     *
+     * High-speed sessions have restrictions:
+     * 1. Maximum 2 surfaces.
+     * 2. All surfaces must have the same size. When the restrictions are not met, this method will
+     *    return null.
+     *
+     * @param surfaceSizes The list of surface sizes.
+     * @return An array of supported frame rate ranges, or null if the input is invalid or no
+     *   supported ranges are found.
+     */
+    public fun getFrameRateRangesFor(surfaceSizes: List<Size>): Array<Range<Int>>? {
+        // High-speed capture sessions have restrictions:
+        // 1. Maximum 2 surfaces.
+        // 2. All surfaces must have the same size.
+        if (surfaceSizes.size !in 1..2 || surfaceSizes.distinct().size != 1) {
+            return null
+        }
+
+        val supportedFpsRanges =
+            getHighSpeedVideoFpsRangesFor(surfaceSizes[0]).takeIf { it.isNotEmpty() } ?: return null
+
+        // For 2 surfaces case, the FPS range must be fixed (lower == upper). See
+        // CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList.
+        return if (surfaceSizes.size == 2) {
+                supportedFpsRanges.filter { it.lower == it.upper }
+            } else {
+                supportedFpsRanges
+            }
+            .toTypedArray()
+    }
+
+    /**
+     * Finds the common elements present in all given lists, preserving the order from the first
+     * list.
+     *
+     * This function takes a list of lists and returns a new list containing only the elements that
+     * appear in every input list. The order of elements in the output list matches their order in
+     * the first list.
+     *
+     * @return A list containing only the elements found in all input lists, ordered according to
+     *   their presence in the first list.
+     */
+    private fun <T> List<List<T>>.findCommonElements(): List<T> {
+        if (isEmpty()) return emptyList()
+
+        val commonElements = this.first().toMutableList()
+        this.drop(1).forEach { commonElements.retainAll(it) }
+        return commonElements
+    }
+
+    private fun getHighSpeedVideoFpsRangesFor(size: Size): List<Range<Int>> {
+        return runCatching { streamConfigurationMapCompat.getHighSpeedVideoFpsRangesFor(size) }
+            .getOrNull()
+            ?.filterNotNull()
+            ?.toList() ?: emptyList()
+    }
+
+    private companion object {
+        private const val TAG = "HighSpeedResolver"
+        private const val SUPPORTED_FORMAT = INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
index d25894f..2bac7fd 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraManager
 import android.hardware.camera2.CameraMetadata
 import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
 import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
 import android.hardware.camera2.params.DynamicRangeProfiles
 import android.hardware.camera2.params.StreamConfigurationMap
@@ -81,11 +82,16 @@
 import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.ImageInputConfig
 import androidx.camera.core.impl.StreamSpec
+import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
+import androidx.camera.core.impl.SurfaceConfig.ConfigSize
+import androidx.camera.core.impl.SurfaceConfig.ConfigType
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.utils.CompareSizesByArea
 import androidx.camera.core.internal.utils.SizeUtil
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1440P
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_720P
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
@@ -174,6 +180,25 @@
             Size(7200, 4050), // 16:9
         )
     private val maximumResolutionHighResolutionSupportedSizes = arrayOf(Size(8000, 6000))
+    private val commonHighSpeedSupportedSizeFpsMap =
+        mapOf(
+            RESOLUTION_1080P to
+                listOf(
+                    Range.create(30, 120),
+                    Range.create(120, 120),
+                    Range.create(30, 240),
+                    Range.create(240, 240),
+                ),
+            RESOLUTION_720P to
+                listOf(
+                    Range.create(30, 120),
+                    Range.create(120, 120),
+                    Range.create(30, 240),
+                    Range.create(240, 240),
+                    Range.create(30, 480),
+                    Range.create(480, 480)
+                )
+        )
 
     private val streamUseCaseOverrideValue = 3L
     private val context = InstrumentationRegistry.getInstrumentation().context
@@ -543,6 +568,69 @@
         }
     }
 
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun checkSurfaceCombinationSupportForHighSpeed() {
+        setupCamera(
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap
+        )
+        val supportedSurfaceCombination =
+            SupportedSurfaceCombination(context, fakeCameraMetadata, mockEncoderProfilesAdapter)
+        val featureSettings =
+            SupportedSurfaceCombination.FeatureSettings(
+                CameraMode.DEFAULT,
+                DynamicRange.BIT_DEPTH_8_BIT,
+                isHighSpeedOn = true
+            )
+
+        // The expected SurfaceConfig is PRIV + RECORD because the max high speed size 1920x1080 is
+        // between PREVIEW and RECORD size.
+        val shouldSupportCombinations =
+            listOf(
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                }
+            )
+        shouldSupportCombinations.forEach {
+            assertThat(
+                    supportedSurfaceCombination.checkSupported(
+                        featureSettings,
+                        it.surfaceConfigList
+                    )
+                )
+                .isTrue()
+        }
+
+        val shouldNotSupportCombinations =
+            listOf(
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW))
+                }
+            )
+        shouldNotSupportCombinations.forEach {
+            assertThat(
+                    supportedSurfaceCombination.checkSupported(
+                        featureSettings,
+                        it.surfaceConfigList
+                    )
+                )
+                .isFalse()
+        }
+    }
+
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Surface config transformation tests
@@ -1506,6 +1594,7 @@
 
     private fun getSuggestedSpecsAndVerify(
         useCasesExpectedResultMap: Map<UseCase, Size>,
+        useCasesOutputSizesMap: Map<UseCase, List<Size>>? = null,
         attachedSurfaceInfoList: List<AttachedSurfaceInfo> = emptyList(),
         hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
         capabilities: IntArray? = null,
@@ -1514,31 +1603,37 @@
         cameraMode: Int = CameraMode.DEFAULT,
         useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap(),
         supportedOutputFormats: IntArray? = null,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
         default10BitProfile: Long? = null,
         isPreviewStabilizationOn: Boolean = false,
-        hasVideoCapture: Boolean = false
+        hasVideoCapture: Boolean = false,
+        targetHighSpeedFpsRange: Range<Int>? = null
     ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
         setupCamera(
             hardwareLevel = hardwareLevel,
             capabilities = capabilities,
             dynamicRangeProfiles = dynamicRangeProfiles,
             default10BitProfile = default10BitProfile,
-            supportedFormats = supportedOutputFormats
+            supportedFormats = supportedOutputFormats,
+            supportedHighSpeedSizeAndFpsMap = supportedHighSpeedSizeAndFpsMap,
         )
         val supportedSurfaceCombination =
             SupportedSurfaceCombination(context, fakeCameraMetadata, mockEncoderProfilesAdapter)
 
         val useCaseConfigMap = getUseCaseToConfigMap(useCasesExpectedResultMap.keys.toList())
         val useCaseConfigToOutputSizesMap =
-            getUseCaseConfigToOutputSizesMap(useCaseConfigMap.values.toList())
+            useCaseConfigMap.entries.associate { (useCase, config) ->
+                config to (useCasesOutputSizesMap?.get(useCase) ?: supportedSizes.toList())
+            }
         val resultPair =
             supportedSurfaceCombination.getSuggestedStreamSpecifications(
                 cameraMode,
                 attachedSurfaceInfoList,
                 useCaseConfigToOutputSizesMap,
                 isPreviewStabilizationOn,
-                hasVideoCapture
+                hasVideoCapture,
+                targetHighSpeedFpsRange
             )
         val suggestedStreamSpecsForNewUseCases = resultPair.first
         val suggestedStreamSpecsForOldSurfaces = resultPair.second
@@ -1612,17 +1707,6 @@
         return useCaseConfigMap
     }
 
-    private fun getUseCaseConfigToOutputSizesMap(
-        useCaseConfigs: List<UseCaseConfig<*>>
-    ): Map<UseCaseConfig<*>, List<Size>> {
-        val resultMap =
-            mutableMapOf<UseCaseConfig<*>, List<Size>>().apply {
-                useCaseConfigs.forEach { put(it, supportedSizes.toList()) }
-            }
-
-        return resultMap
-    }
-
     /** Helper function that returns whether size is <= maxSize */
     private fun sizeIsAtMost(size: Size, maxSize: Size): Boolean {
         return (size.height * size.width) <= (maxSize.height * maxSize.width)
@@ -3242,6 +3326,137 @@
             .isFalse()
     }
 
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_returnsCorrectSizeAndFpsRange() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW, surfaceOccupancyPriority = 2)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE, surfaceOccupancyPriority = 5)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_VGA, RESOLUTION_1080P, RESOLUTION_720P),
+                videoUseCase to listOf(RESOLUTION_1440P, RESOLUTION_720P, RESOLUTION_1080P)
+            )
+        // videoUseCase has higher surface priority so the expected size should be the first
+        // common size of videoUseCases. i.e. RESOLUTION_720P.
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_720P, videoUseCase to RESOLUTION_720P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap,
+            targetHighSpeedFpsRange = Range.create(240, 240),
+            compareExpectedFps = Range.create(240, 240)
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_singleSurface_returnsCorrectSizeAndClosestFps() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val useCasesOutputSizesMap = mapOf(previewUseCase to listOf(RESOLUTION_1080P))
+        val useCaseExpectedResultMap = mapOf(previewUseCase to RESOLUTION_1080P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap,
+            targetHighSpeedFpsRange = Range.create(30, 480),
+            compareExpectedFps = Range.create(30, 240) // Find the closest supported fps.
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_multipleSurfaces_returnsCorrectSizeAndClosetMaxFps() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_1080P),
+                videoUseCase to listOf(RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_1080P, videoUseCase to RESOLUTION_1080P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap,
+            targetHighSpeedFpsRange = Range.create(30, 480),
+            compareExpectedFps = Range.create(240, 240) // Find the closest max supported fps.
+        )
+    }
+
+    @Config(minSdk = 21, maxSdk = 22)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_unsupportedSdkVersion_throwException() {
+        val useCase = createUseCase(CaptureType.PREVIEW)
+        val useCaseExpectedResultMap = mapOf(useCase to RESOLUTION_1080P)
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_noCommonSize_throwException() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_VGA, RESOLUTION_720P),
+                videoUseCase to listOf(RESOLUTION_1440P, RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_VGA, videoUseCase to RESOLUTION_1440P)
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                useCasesOutputSizesMap = useCasesOutputSizesMap,
+                supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap,
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_tooManyUseCases_throwException() {
+        val previewUseCase1 = createUseCase(CaptureType.PREVIEW)
+        val previewUseCase2 = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase1 to listOf(RESOLUTION_1080P),
+                previewUseCase2 to listOf(RESOLUTION_1080P),
+                videoUseCase to listOf(RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(
+                previewUseCase1 to RESOLUTION_1080P,
+                previewUseCase2 to RESOLUTION_1080P,
+                videoUseCase to RESOLUTION_1080P
+            )
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                useCasesOutputSizesMap = useCasesOutputSizesMap,
+                supportedHighSpeedSizeAndFpsMap = commonHighSpeedSupportedSizeFpsMap,
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
     private fun setupCamera(
         hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
         sensorOrientation: Int = sensorOrientation90,
@@ -3249,6 +3464,7 @@
         supportedSizes: Array<Size> = this.supportedSizes,
         supportedFormats: IntArray? = null,
         highResolutionSupportedSizes: Array<Size>? = null,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? = null,
         maximumResolutionSupportedSizes: Array<Size>? = null,
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
@@ -3374,6 +3590,36 @@
             }
         }
 
+        if (
+            supportedHighSpeedSizeAndFpsMap != null &&
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+        ) {
+            // Mock highSpeedVideoSizes
+            whenever(mockMap.highSpeedVideoSizes)
+                .thenReturn(supportedHighSpeedSizeAndFpsMap.keys.toTypedArray())
+
+            // Mock highSpeedVideoFpsRanges
+            val allFpsRanges = supportedHighSpeedSizeAndFpsMap.values.flatten().distinct()
+            whenever(mockMap.highSpeedVideoFpsRanges).thenReturn(allFpsRanges.toTypedArray())
+
+            // Mock getHighSpeedVideoSizesFor
+            allFpsRanges.forEach { fpsRange ->
+                val sizesForRange =
+                    supportedHighSpeedSizeAndFpsMap.entries
+                        .filter { (_, fpsRanges) -> fpsRanges.contains(fpsRange) }
+                        .map { it.key }
+                        .sortedWith(CompareSizesByArea(false)) // Descending order
+                        .toTypedArray()
+                whenever(mockMap.getHighSpeedVideoSizesFor(fpsRange)).thenReturn(sizesForRange)
+            }
+
+            // Mock getHighSpeedVideoFpsRangesFor
+            supportedHighSpeedSizeAndFpsMap.forEach { (size, fpsRanges) ->
+                whenever(mockMap.getHighSpeedVideoFpsRangesFor(size))
+                    .thenReturn(fpsRanges.toTypedArray())
+            }
+        }
+
         // setup to return different minimum frame durations depending on resolution
         // minimum frame durations were designated only for the purpose of testing
         Mockito.`when`(
@@ -3528,9 +3774,16 @@
     private fun createUseCase(
         captureType: UseCaseConfigFactory.CaptureType,
         targetFrameRate: Range<Int>? = null,
-        dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED
+        dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED,
+        surfaceOccupancyPriority: Int? = null
     ): UseCase {
-        return createUseCase(captureType, targetFrameRate, dynamicRange, false)
+        return createUseCase(
+            captureType,
+            targetFrameRate,
+            dynamicRange,
+            streamUseCaseOverride = false,
+            surfaceOccupancyPriority = surfaceOccupancyPriority
+        )
     }
 
     private fun createUseCase(
@@ -3538,7 +3791,8 @@
         targetFrameRate: Range<Int>? = null,
         dynamicRange: DynamicRange? = DynamicRange.UNSPECIFIED,
         streamUseCaseOverride: Boolean = false,
-        imageFormat: Int? = null
+        imageFormat: Int? = null,
+        surfaceOccupancyPriority: Int? = null,
     ): UseCase {
         val builder =
             FakeUseCaseConfig.Builder(
@@ -3560,6 +3814,9 @@
         if (streamUseCaseOverride) {
             builder.mutableConfig.insertOption(streamUseCaseOption, streamUseCaseOverrideValue)
         }
+
+        surfaceOccupancyPriority?.let { builder.setSurfaceOccupancyPriority(it) }
+
         return builder.build()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolverTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolverTest.kt
new file mode 100644
index 0000000..1a1aa3d
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/HighSpeedResolverTest.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.graphics.ImageFormat
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
+import android.hardware.camera2.CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
+import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.utils.CompareSizesByArea
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_720P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_2160P
+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.mockito.Mockito
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class HighSpeedResolverTest {
+
+    private companion object {
+        private const val CAMERA_ID = "0"
+
+        private const val FPS_30 = 30
+        private const val FPS_120 = 120
+        private const val FPS_240 = 240
+        private const val FPS_480 = 480
+
+        private val RANGE_30_120 = Range.create(FPS_30, FPS_120)
+        private val RANGE_120_120 = Range.create(FPS_120, FPS_120)
+        private val RANGE_30_240 = Range.create(FPS_30, FPS_240)
+        private val RANGE_240_240 = Range.create(FPS_240, FPS_240)
+        private val RANGE_30_480 = Range.create(FPS_30, FPS_480)
+        private val RANGE_480_480 = Range.create(FPS_480, FPS_480)
+
+        private const val FORMAT_PRIVATE =
+            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+
+        private val COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP =
+            mapOf(
+                RESOLUTION_1080P to
+                    listOf(
+                        RANGE_30_120,
+                        RANGE_120_120,
+                        RANGE_30_240,
+                        RANGE_240_240,
+                    ),
+                RESOLUTION_720P to
+                    listOf(
+                        RANGE_30_120,
+                        RANGE_120_120,
+                        RANGE_30_240,
+                        RANGE_240_240,
+                        RANGE_30_480,
+                        RANGE_480_480
+                    )
+            )
+    }
+
+    private val defaultHighSpeedResolver = createHighSpeedResolver()
+    private val emptyHighSpeedResolver =
+        createHighSpeedResolver(
+            characteristics = createCharacteristicsMap(supportedHighSpeedSizeAndFpsMap = emptyMap())
+        )
+
+    @Test
+    fun filterCommonSupportedSizes_returnsCorrectMap() {
+        val useCaseSupportedSizeMap =
+            listOf(
+                    listOf(RESOLUTION_480P, RESOLUTION_720P, RESOLUTION_1080P),
+                    listOf(RESOLUTION_1080P, RESOLUTION_720P, RESOLUTION_2160P),
+                    listOf(RESOLUTION_480P, RESOLUTION_720P, RESOLUTION_1080P, RESOLUTION_VGA)
+                )
+                .toUseCaseSupportedSizeMap()
+
+        val result = defaultHighSpeedResolver.filterCommonSupportedSizes(useCaseSupportedSizeMap)
+
+        // Assert: return common sizes and preserve the original order.
+        assertThat(result.values)
+            .containsExactly(
+                listOf(RESOLUTION_720P, RESOLUTION_1080P),
+                listOf(RESOLUTION_1080P, RESOLUTION_720P),
+                listOf(RESOLUTION_720P, RESOLUTION_1080P)
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun getMaxSize_noSupportedSizes_returnsNull() {
+        val result = emptyHighSpeedResolver.maxSize
+
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun getMaxSize_supportedSizesExist_returnsLargestSize() {
+        val result = defaultHighSpeedResolver.maxSize
+
+        assertThat(result).isEqualTo(RESOLUTION_1080P)
+    }
+
+    @Test
+    fun getMaxFrameRate_unsupportedImageFormat_returnsZero() {
+        val result = defaultHighSpeedResolver.getMaxFrameRate(ImageFormat.JPEG, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(0)
+    }
+
+    @Test
+    fun getMaxFrameRate_noSupportedFpsRanges_returnsZero() {
+        val result = emptyHighSpeedResolver.getMaxFrameRate(FORMAT_PRIVATE, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(0)
+    }
+
+    @Test
+    fun getMaxFrameRate_supportedFpsRangesExist_returnMaxFps() {
+        val result = defaultHighSpeedResolver.getMaxFrameRate(FORMAT_PRIVATE, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(FPS_240)
+    }
+
+    @Test
+    fun getSizeArrangements_emptyInput_returnEmptyList() {
+        val sizeArrangements = defaultHighSpeedResolver.getSizeArrangements(emptyList())
+
+        assertThat(sizeArrangements).isEmpty()
+    }
+
+    @Test
+    fun getSizeArrangements_hasCommonSizes_returnCorrectArrangements() {
+        val common1080p = RESOLUTION_1080P
+        val common720p = RESOLUTION_720P
+        val supportedOutputSizesList =
+            listOf(
+                listOf(RESOLUTION_480P, common720p, common1080p),
+                listOf(common1080p, common720p, RESOLUTION_2160P),
+                listOf(RESOLUTION_480P, common720p, common1080p, RESOLUTION_VGA)
+            )
+        val sizeArrangements =
+            defaultHighSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+
+        assertThat(sizeArrangements)
+            .containsExactly(
+                listOf(common720p, common720p, common720p),
+                listOf(common1080p, common1080p, common1080p)
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun getSizeArrangements_noCommonSizes_returnEmptyList() {
+        val supportedOutputSizesList =
+            listOf(
+                listOf(RESOLUTION_480P, RESOLUTION_720P),
+                listOf(RESOLUTION_1080P, RESOLUTION_2160P),
+                listOf(RESOLUTION_1080P, RESOLUTION_720P)
+            )
+
+        val result = defaultHighSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_invalidInput_returnsNull() {
+        // More than 2 surfaces.
+        assertThat(
+                defaultHighSpeedResolver.getFrameRateRangesFor(
+                    listOf(RESOLUTION_720P, RESOLUTION_720P, RESOLUTION_720P)
+                )
+            )
+            .isNull()
+
+        // Different sizes.
+        assertThat(
+                defaultHighSpeedResolver.getFrameRateRangesFor(
+                    listOf(RESOLUTION_720P, RESOLUTION_1080P)
+                )
+            )
+            .isNull()
+
+        // Empty list.
+        assertThat(defaultHighSpeedResolver.getFrameRateRangesFor(emptyList())).isNull()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_noSupportedSizes_returnsNull() {
+        assertThat(emptyHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P))).isNull()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_oneSurface_returnsAllSupportedRanges() {
+        val result = defaultHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P))
+
+        assertThat(result!!.toList())
+            .containsExactly(
+                RANGE_30_120,
+                RANGE_120_120,
+                RANGE_30_240,
+                RANGE_240_240,
+                RANGE_30_480,
+                RANGE_480_480
+            )
+    }
+
+    @Test
+    fun getFrameRateRangesFor_twoSurfaces_returnsFixedFpsRanges() {
+        val result =
+            defaultHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P, RESOLUTION_720P))
+
+        assertThat(result!!.toList())
+            .containsExactly(RANGE_120_120, RANGE_240_240, RANGE_480_480)
+            .inOrder()
+    }
+
+    private fun createHighSpeedResolver(
+        cameraId: CameraId = CameraId(CAMERA_ID),
+        characteristics: Map<CameraCharacteristics.Key<*>, Any?> = createCharacteristicsMap(),
+    ): HighSpeedResolver {
+        return HighSpeedResolver(
+            cameraMetadata =
+                FakeCameraMetadata(cameraId = cameraId, characteristics = characteristics)
+        )
+    }
+
+    private fun createCharacteristicsMap(
+        hardwareLevel: Int = INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? =
+            COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+    ): Map<CameraCharacteristics.Key<*>, Any?> {
+        val mockMap =
+            Mockito.mock(StreamConfigurationMap::class.java).also { map ->
+                if (supportedHighSpeedSizeAndFpsMap != null) {
+                    // Mock highSpeedVideoSizes
+                    Mockito.`when`(map.highSpeedVideoSizes)
+                        .thenReturn(supportedHighSpeedSizeAndFpsMap.keys.toTypedArray())
+
+                    // Mock highSpeedVideoFpsRanges
+                    val allFpsRanges = supportedHighSpeedSizeAndFpsMap.values.flatten().distinct()
+                    Mockito.`when`(map.highSpeedVideoFpsRanges)
+                        .thenReturn(allFpsRanges.toTypedArray())
+
+                    // Mock getHighSpeedVideoSizesFor
+                    allFpsRanges.forEach { fpsRange ->
+                        val sizesForRange =
+                            supportedHighSpeedSizeAndFpsMap.entries
+                                .filter { (_, fpsRanges) -> fpsRanges.contains(fpsRange) }
+                                .map { it.key }
+                                .sortedWith(CompareSizesByArea(false)) // Descending order
+                                .toTypedArray()
+                        Mockito.`when`(map.getHighSpeedVideoSizesFor(fpsRange))
+                            .thenReturn(sizesForRange)
+                    }
+
+                    // Mock getHighSpeedVideoFpsRangesFor
+                    supportedHighSpeedSizeAndFpsMap.forEach { (size, fpsRanges) ->
+                        Mockito.`when`(map.getHighSpeedVideoFpsRangesFor(size))
+                            .thenReturn(fpsRanges.toTypedArray())
+                    }
+                }
+            }
+
+        return mutableMapOf<CameraCharacteristics.Key<*>, Any?>(
+            INFO_SUPPORTED_HARDWARE_LEVEL to hardwareLevel,
+            SCALER_STREAM_CONFIGURATION_MAP to mockMap
+        )
+    }
+
+    private fun List<List<Size>>.toUseCaseSupportedSizeMap(): Map<UseCaseConfig<*>, List<Size>> {
+        return associate { sizes ->
+            FakeUseCaseConfig.Builder(CaptureType.PREVIEW, FORMAT_PRIVATE).build().currentConfig to
+                sizes
+        }
+    }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/HighSpeedCaptureSessionTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/HighSpeedCaptureSessionTest.kt
new file mode 100644
index 0000000..38e90b1
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/HighSpeedCaptureSessionTest.kt
@@ -0,0 +1,412 @@
+/*
+ * 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.camera2.internal
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
+import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CaptureResult.CONTROL_AE_TARGET_FPS_RANGE
+import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
+import android.media.CamcorderProfile
+import android.media.CamcorderProfile.QUALITY_HIGH_SPEED_1080P
+import android.media.CamcorderProfile.QUALITY_HIGH_SPEED_2160P
+import android.media.CamcorderProfile.QUALITY_HIGH_SPEED_480P
+import android.media.CamcorderProfile.QUALITY_HIGH_SPEED_720P
+import android.media.MediaCodec
+import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
+import android.media.MediaFormat
+import android.media.MediaFormat.KEY_BIT_RATE
+import android.media.MediaFormat.KEY_COLOR_FORMAT
+import android.media.MediaFormat.KEY_FRAME_RATE
+import android.media.MediaFormat.KEY_I_FRAME_INTERVAL
+import android.media.MediaFormat.MIMETYPE_VIDEO_AVC
+import android.media.MediaFormat.MIMETYPE_VIDEO_H263
+import android.media.MediaFormat.MIMETYPE_VIDEO_HEVC
+import android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4
+import android.media.MediaFormat.MIMETYPE_VIDEO_VP8
+import android.media.MediaFormat.MIMETYPE_VIDEO_VP9
+import android.media.MediaRecorder.VideoEncoder.H263
+import android.media.MediaRecorder.VideoEncoder.H264
+import android.media.MediaRecorder.VideoEncoder.HEVC
+import android.media.MediaRecorder.VideoEncoder.MPEG_4_SP
+import android.media.MediaRecorder.VideoEncoder.VP8
+import android.media.MediaRecorder.VideoEncoder.VP9
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.Log
+import android.util.Range
+import android.util.Size
+import android.view.Surface
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.internal.SynchronizedCaptureSession.OpenerBuilder
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.camera2.internal.compat.params.DynamicRangesCompat
+import androidx.camera.camera2.internal.compat.quirk.CameraQuirks
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks
+import androidx.camera.core.impl.CameraCaptureCallback
+import androidx.camera.core.impl.CameraCaptureResult
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.ImmediateSurface
+import androidx.camera.core.impl.Quirks
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.futures.Futures
+import androidx.camera.testing.impl.CameraUtil
+import androidx.camera.testing.impl.CameraUtil.CameraDeviceHolder
+import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
+import org.junit.AssumptionViolatedException
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+class HighSpeedCaptureSessionTest {
+
+    @get:Rule
+    val cameraRule =
+        CameraUtil.grantCameraPermissionAndPreTestAndPostTest(
+            PreTestCameraIdList(Camera2Config.defaultConfig())
+        )
+
+    private lateinit var mCameraDeviceHolder: CameraDeviceHolder
+    private lateinit var mCameraCharacteristics: CameraCharacteristicsCompat
+    private lateinit var mDynamicRangesCompat: DynamicRangesCompat
+    private lateinit var cameraQuirks: Quirks
+    private lateinit var captureSessionOpenerBuilder: OpenerBuilder
+    private lateinit var cameraId: String
+
+    private val mCaptureSessions = mutableListOf<CaptureSession>()
+    private val mDeferrableSurfaces = mutableListOf<DeferrableSurface>()
+
+    private val isHighSpeedSupported: Boolean
+        get() {
+            val capabilities =
+                mCameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
+            return capabilities?.any {
+                it == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+            } == true
+        }
+
+    @Before
+    fun setup() {
+        val handler = Handler(handlerThread.getLooper())
+        val executor = CameraXExecutors.newHandlerExecutor(handler)
+        val scheduledExecutor = CameraXExecutors.newHandlerExecutor(handler)
+
+        cameraId = CameraUtil.getBackwardCompatibleCameraIdListOrThrow()[0]
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val cameraManager = CameraManagerCompat.from(context, handler)
+        try {
+            mCameraCharacteristics = cameraManager.getCameraCharacteristicsCompat(cameraId)
+        } catch (e: CameraAccessExceptionCompat) {
+            throw AssumptionViolatedException("Could not retrieve camera characteristics", e)
+        }
+
+        cameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristics)
+        mDynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(mCameraCharacteristics)
+
+        val captureSessionRepository = CaptureSessionRepository(executor)
+        captureSessionOpenerBuilder =
+            OpenerBuilder(
+                executor,
+                scheduledExecutor,
+                handler,
+                captureSessionRepository,
+                CameraQuirks.get(cameraId, mCameraCharacteristics),
+                DeviceQuirks.getAll()
+            )
+
+        mCameraDeviceHolder =
+            CameraUtil.getCameraDevice(cameraId, captureSessionRepository.cameraStateCallback)
+    }
+
+    @After
+    fun tearDown() {
+        // Ensure all capture sessions are fully closed
+        val releaseFutures = mutableListOf<ListenableFuture<Void?>?>()
+        for (captureSession in mCaptureSessions) {
+            releaseFutures.add(captureSession.release(/* abortInFlightCaptures= */ false))
+        }
+        mCaptureSessions.clear()
+        Futures.allAsList<Void?>(releaseFutures).get(10L, TimeUnit.SECONDS)
+
+        if (this::mCameraDeviceHolder.isInitialized) {
+            CameraUtil.releaseCameraDevice(mCameraDeviceHolder)
+        }
+
+        for (deferrableSurface in mDeferrableSurfaces) {
+            deferrableSurface.close()
+        }
+    }
+
+    @Test
+    fun issueCaptureRequest_forRecording_canIssueRepeatingAndSingleRequests() {
+        // Arrange: check capability and get supported high speed size and fps range.
+        assumeTrue(isHighSpeedSupported)
+
+        val profile =
+            getHighSpeedCamcorderProfile()
+                ?: throw AssumptionViolatedException("No CamcorderProfile")
+        val profileInfo =
+            "video codec:${profile.videoCodec}, " +
+                "size:${profile.videoSize}, " +
+                "frame rate:${profile.videoFrameRate}, " +
+                "bit rate:${profile.videoBitRate}"
+        Log.d(TAG, "Selected profile $profileInfo")
+
+        // Create SessionConfig for high-speed capture
+        val repeatingLatch = CountDownLatch(2)
+        val templateType = TEMPLATE_RECORD
+        val previewSurface = createSurfaceTextureDeferrableSurface(profile.videoSize)
+        val videoSurface = createMediaCodecDeferrableSurface(profile)
+        val fpsRange = Range.create(profile.videoFrameRate, profile.videoFrameRate)
+        val sessionConfig =
+            SessionConfig.Builder()
+                .apply {
+                    setSessionType(SESSION_HIGH_SPEED)
+                    setTemplateType(templateType)
+                    addSurface(previewSurface)
+                    addSurface(videoSurface)
+                    setExpectedFrameRateRange(fpsRange)
+                    addCameraCaptureCallback(
+                        object : CameraCaptureCallback() {
+                            override fun onCaptureCompleted(
+                                captureConfigId: Int,
+                                cameraCaptureResult: CameraCaptureResult
+                            ) {
+                                val fps =
+                                    cameraCaptureResult.captureResult?.get(
+                                        CONTROL_AE_TARGET_FPS_RANGE
+                                    )
+                                if (repeatingLatch.count > 0L) {
+                                    Log.d(TAG, "Repeating onCaptureCompleted: fps = $fps")
+                                }
+                                if (fps == fpsRange) {
+                                    repeatingLatch.countDown()
+                                }
+                            }
+                        }
+                    )
+                }
+                .build()
+
+        // Act: open capture session and issue repeating request via SessionConfig.
+        val captureSession =
+            createCaptureSession().apply {
+                open(
+                    sessionConfig,
+                    mCameraDeviceHolder.get()!!,
+                    captureSessionOpenerBuilder.build()
+                )
+                this.sessionConfig = sessionConfig
+            }
+
+        // Assert.
+        assertWithMessage("Failed to issue repeating request by $profileInfo")
+            .that(repeatingLatch.await(10, TimeUnit.SECONDS))
+            .isTrue()
+
+        // Arrange: create CaptureConfig.
+        val captureId = 100
+        val captureLatch = CountDownLatch(1)
+        val captureConfig =
+            CaptureConfig.Builder()
+                .apply {
+                    this.templateType = templateType
+                    addSurface(previewSurface)
+                    addSurface(videoSurface)
+                    setId(captureId)
+                    addCameraCaptureCallback(
+                        object : CameraCaptureCallback() {
+                            override fun onCaptureCompleted(
+                                captureConfigId: Int,
+                                cameraCaptureResult: CameraCaptureResult
+                            ) {
+                                // Count down when the request is proceeded and fps is applied.
+                                val fps =
+                                    cameraCaptureResult.captureResult?.get(
+                                        CONTROL_AE_TARGET_FPS_RANGE
+                                    )
+                                Log.d(
+                                    TAG,
+                                    "Single capture onCaptureCompleted: " +
+                                        "captureConfigId = $captureConfigId, fps = $fps"
+                                )
+                                if (captureId == captureConfigId && fps == fpsRange) {
+                                    captureLatch.countDown()
+                                }
+                            }
+                        }
+                    )
+                }
+                .build()
+
+        // Act. issue single request.
+        captureSession.issueCaptureRequests(listOf(captureConfig))
+
+        // Assert.
+        assertWithMessage("Failed to issue single capture request by $profileInfo")
+            .that(captureLatch.await(5, TimeUnit.SECONDS))
+            .isTrue()
+    }
+
+    private fun createCaptureSession() =
+        CaptureSession(mDynamicRangesCompat, cameraQuirks).also { mCaptureSessions.add(it) }
+
+    private fun getHighSpeedCamcorderProfile(): CamcorderProfile? {
+        return listOf(
+                QUALITY_HIGH_SPEED_480P,
+                QUALITY_HIGH_SPEED_720P,
+                QUALITY_HIGH_SPEED_1080P,
+                QUALITY_HIGH_SPEED_2160P
+            )
+            .filter { CamcorderProfile.hasProfile(it) }
+            .firstNotNullOfOrNull { quality ->
+                @Suppress("DEPRECATION") CamcorderProfile.get(cameraId.toInt(), quality)
+            }
+    }
+
+    private fun createSurfaceTextureDeferrableSurface(size: Size): DeferrableSurface {
+        val surfaceTexture =
+            SurfaceTexture(0).apply {
+                setDefaultBufferSize(size.width, size.height)
+                detachFromGLContext()
+            }
+        val surface = Surface(surfaceTexture)
+        return ImmediateSurface(surface).apply {
+            terminationFuture.addListener(
+                Runnable {
+                    surface.release()
+                    surfaceTexture.release()
+                },
+                CameraXExecutors.directExecutor()
+            )
+            mDeferrableSurfaces.add(this)
+        }
+    }
+
+    private fun createMediaCodecDeferrableSurface(profile: CamcorderProfile): DeferrableSurface {
+        val surface = MediaCodec.createPersistentInputSurface()
+        val mimeType = profile.videoMime
+        val codec = MediaCodec.createEncoderByType(mimeType)
+        codec.setCallback(emptyCodecCallback)
+        val format =
+            MediaFormat.createVideoFormat(
+                    mimeType,
+                    profile.videoFrameWidth,
+                    profile.videoFrameHeight
+                )
+                .apply {
+                    setInteger(KEY_COLOR_FORMAT, COLOR_FormatSurface)
+                    setInteger(KEY_FRAME_RATE, profile.videoFrameRate)
+                    setInteger(KEY_BIT_RATE, profile.videoBitRate) // 4 Mbps
+                    setInteger(KEY_I_FRAME_INTERVAL, 1) // 1 second
+                }
+        codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
+        codec.setInputSurface(surface)
+        codec.start()
+
+        return ImmediateSurface(surface).apply {
+            terminationFuture.addListener(
+                Runnable {
+                    codec.stop()
+                    codec.release()
+                    surface.release()
+                },
+                CameraXExecutors.directExecutor()
+            )
+            mDeferrableSurfaces.add(this)
+        }
+    }
+
+    private val CamcorderProfile.videoMime
+        get() =
+            when (videoCodec) {
+                H263 -> MIMETYPE_VIDEO_H263
+                H264 -> MIMETYPE_VIDEO_AVC
+                MPEG_4_SP -> MIMETYPE_VIDEO_MPEG4
+                HEVC -> MIMETYPE_VIDEO_HEVC
+                VP8 -> MIMETYPE_VIDEO_VP8
+                VP9 -> MIMETYPE_VIDEO_VP9
+                else -> throw IllegalArgumentException("Unsupported video codec: $videoCodec")
+            }
+
+    private val CamcorderProfile.videoSize
+        get() = Size(videoFrameWidth, videoFrameHeight)
+
+    private val emptyCodecCallback by lazy {
+        object : MediaCodec.Callback() {
+            override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {}
+
+            override fun onOutputBufferAvailable(
+                codec: MediaCodec,
+                index: Int,
+                info: MediaCodec.BufferInfo
+            ) {
+                codec.getOutputBuffer(index)
+                codec.releaseOutputBuffer(index, false)
+            }
+
+            override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
+                fail(e.message)
+            }
+
+            override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {}
+        }
+    }
+
+    private companion object {
+        private const val TAG = "HighSpeedCaptureSessionTest"
+        private lateinit var handlerThread: HandlerThread
+
+        @BeforeClass
+        @JvmStatic
+        fun setUpClass() {
+            handlerThread = HandlerThread("HighSpeedCaptureSessionTest")
+            handlerThread.start()
+        }
+
+        @AfterClass
+        @JvmStatic
+        fun tearDownClass() {
+            if (this::handlerThread.isInitialized) {
+                handlerThread.quitSafely()
+            }
+        }
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 7aeb76c..7589dc8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -1246,7 +1246,7 @@
 
         try {
             mSupportedSurfaceCombination.getSuggestedStreamSpecifications(cameraMode,
-                    attachedSurfaces, useCaseConfigToSizeMap, false, false);
+                    attachedSurfaces, useCaseConfigToSizeMap, false, false, null);
         } catch (IllegalArgumentException e) {
             debugLog("Surface combination with metering repeating  not supported!", e);
             return false;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
index f858f4e..e687de9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
@@ -189,6 +189,7 @@
                 existingSurfaces,
                 newUseCaseConfigsSupportedSizeMap,
                 isPreviewStabilizationOn,
-                hasVideoCapture);
+                hasVideoCapture,
+                null);
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 865587b..95d9e1c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.MultiResolutionStreamInfo;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Build;
 import android.view.Surface;
 
@@ -227,7 +228,6 @@
         }
     }
 
-
     /**
      * {@inheritDoc}
      */
@@ -705,8 +705,15 @@
                         mRequestMonitor.createMonitorListener(createCamera2CaptureCallback(
                                 captureConfig.getCameraCaptureCallbacks()));
 
-                return mSynchronizedCaptureSession.setSingleRepeatingRequest(captureRequest,
-                        comboCaptureCallback);
+                if (sessionConfig.getSessionType() == SessionConfiguration.SESSION_HIGH_SPEED) {
+                    List<CaptureRequest> requests =
+                            mSynchronizedCaptureSession.createHighSpeedRequestList(captureRequest);
+                    return mSynchronizedCaptureSession.setRepeatingBurstRequests(requests,
+                            comboCaptureCallback);
+                } else {  // SessionConfiguration.SESSION_REGULAR
+                    return mSynchronizedCaptureSession.setSingleRepeatingRequest(captureRequest,
+                            comboCaptureCallback);
+                }
             } catch (CameraAccessException e) {
                 Logger.e(TAG, "Unable to access camera: " + e.getMessage());
                 Thread.dumpStack();
@@ -857,8 +864,13 @@
                                     }
                                 }));
                     }
-                    return mSynchronizedCaptureSession.captureBurstRequests(captureRequests,
-                            callbackAggregator);
+                    if (mSessionConfig != null && mSessionConfig.getSessionType()
+                            == SessionConfiguration.SESSION_HIGH_SPEED) {
+                        return captureHighSpeedBurst(captureRequests, callbackAggregator);
+                    } else {  // SessionConfiguration.SESSION_REGULAR
+                        return mSynchronizedCaptureSession.captureBurstRequests(captureRequests,
+                                callbackAggregator);
+                    }
                 } else {
                     Logger.d(TAG,
                             "Skipping issuing burst request due to no valid request elements");
@@ -872,6 +884,40 @@
         }
     }
 
+    @GuardedBy("mSessionLock")
+    private int captureHighSpeedBurst(@NonNull List<CaptureRequest> captureRequests,
+            @NonNull CameraBurstCaptureCallback callbackAggregator)
+            throws CameraAccessException {
+        // Create a new CameraBurstCaptureCallback to handle callbacks from high-speed requests.
+        // This is necessary because high-speed capture sessions generate multiple requests for
+        // each original request, and we need to map the callbacks back to the original requests.
+        CameraBurstCaptureCallback highSpeedCallbackAggregator = new CameraBurstCaptureCallback();
+
+        int sequenceId = -1;
+
+        for (CaptureRequest captureRequest : captureRequests) {
+            List<CaptureRequest> highSpeedRequests =
+                    Objects.requireNonNull(mSynchronizedCaptureSession)
+                            .createHighSpeedRequestList(captureRequest);
+
+            // For each high-speed request, create a forwarding callback that maps the high-speed
+            // request back to the original request and forwards the callback to the original
+            // callback aggregator.
+            for (CaptureRequest highSpeedRequest : highSpeedRequests) {
+                CaptureCallback forwardingCallback = new RequestForwardingCaptureCallback(
+                        captureRequest, callbackAggregator);
+                highSpeedCallbackAggregator.addCamera2Callbacks(highSpeedRequest,
+                        Collections.singletonList(forwardingCallback));
+            }
+
+            sequenceId = mSynchronizedCaptureSession.captureBurstRequests(
+                    highSpeedRequests, highSpeedCallbackAggregator);
+        }
+
+        // Return the sequence ID of the last burst capture as a representative ID.
+        return sequenceId;
+    }
+
     /**
      * Discards all captures currently pending and in-progress as fast as possible.
      */
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
index 215ddd2..16a544d 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
@@ -19,12 +19,16 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraMetadata;
 import android.os.Build;
+import android.util.Size;
 
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.CameraMode;
+import androidx.camera.core.impl.ImageFormatConstants;
 import androidx.camera.core.impl.SurfaceCombination;
 import androidx.camera.core.impl.SurfaceConfig;
 import androidx.camera.core.impl.SurfaceConfig.ConfigSize;
 import androidx.camera.core.impl.SurfaceConfig.ConfigType;
+import androidx.camera.core.impl.SurfaceSizeDefinition;
 
 import org.jspecify.annotations.NonNull;
 
@@ -972,4 +976,38 @@
         }
         return surfaceCombinations;
     }
+
+    /**
+     * Returns the supported stream combinations for high-speed sessions.
+     */
+    public static @NonNull List<SurfaceCombination> generateHighSpeedSupportedCombinationList(
+            @NonNull Size maxSupportedSize,
+            @NonNull SurfaceSizeDefinition surfaceSizeDefinition) {
+        List<SurfaceCombination> surfaceCombinations = new ArrayList<>();
+
+        // Find the closest SurfaceConfig that can contain the max supported size. Ultimately,
+        // the target resolution still needs to be verified by the StreamConfigurationMap API for
+        // high-speed.
+        SurfaceConfig surfaceConfig = SurfaceConfig.transformSurfaceConfig(CameraMode.DEFAULT,
+                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, maxSupportedSize,
+                surfaceSizeDefinition);
+
+        // Create high-speed supported combinations based on the constraints:
+        // - Only support preview and/or video surface.
+        // - Maximum 2 surfaces.
+        // - All surfaces must have the same size.
+
+        // PRIV
+        SurfaceCombination surfaceCombination = new SurfaceCombination();
+        surfaceCombination.addSurfaceConfig(surfaceConfig);
+        surfaceCombinations.add(surfaceCombination);
+
+        // PRIV + PRIV
+        surfaceCombination = new SurfaceCombination();
+        surfaceCombination.addSurfaceConfig(surfaceConfig);
+        surfaceCombination.addSurfaceConfig(surfaceConfig);
+        surfaceCombinations.add(surfaceCombination);
+
+        return surfaceCombinations;
+    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/HighSpeedResolver.kt b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/HighSpeedResolver.kt
new file mode 100644
index 0000000..15311a0
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/HighSpeedResolver.kt
@@ -0,0 +1,193 @@
+/*
+ * 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
+
+import android.graphics.ImageFormat.PRIVATE
+import android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
+import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+import android.os.Build
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.core.Logger
+import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+import androidx.camera.core.internal.utils.SizeUtil.getArea
+
+/** A class responsible for resolving parameters for high-speed session scenario. */
+public class HighSpeedResolver(private val characteristics: CameraCharacteristicsCompat) {
+
+    /** Indicates whether the camera supports high-speed session. */
+    public val isHighSpeedSupported: Boolean by lazy {
+        Build.VERSION.SDK_INT >= 23 &&
+            characteristics.get(REQUEST_AVAILABLE_CAPABILITIES)?.any {
+                it == REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+            } == true
+    }
+
+    /** The maximum supported size based on area, or `null` if there are no supported sizes. */
+    public val maxSize: Size? by lazy {
+        supportedSizes.takeIf { it.isNotEmpty() }?.maxBy { getArea(it) }
+    }
+
+    private val supportedSizes: List<Size> by lazy {
+        characteristics.streamConfigurationMapCompat.highSpeedVideoSizes?.filterNotNull()
+            ?: emptyList()
+    }
+
+    /**
+     * Filters supported sizes for each use case, retaining only the sizes common to all use cases
+     * and present in the overall supported sizes.
+     *
+     * This function analyzes a map of use case configurations and their corresponding lists of
+     * supported sizes. It identifies the sizes common to all use cases and filters each use case's
+     * supported sizes, retaining only those that are both common across all use cases and present
+     * in the `supportedSizes` list. The original order of the supported sizes for each use case is
+     * preserved.
+     *
+     * @param sizesMap A map where keys represent use case configurations and values are lists of
+     *   `Size` objects representing the supported sizes for each use case.
+     * @return A new map with the same keys as the input `sizesMap`, but with the values (lists of
+     *   sizes) filtered to contain only the common supported sizes that are also present in the
+     *   `supportedSizes` list, while maintaining the original order.
+     */
+    public fun <T> filterCommonSupportedSizes(sizesMap: Map<T, List<Size>>): Map<T, List<Size>> {
+        val commonSupportedSizes =
+            sizesMap.values.toList().findCommonElements().filter { it in supportedSizes }
+        return sizesMap.mapValues { (_, sizes) -> sizes.filter { it in commonSupportedSizes } }
+    }
+
+    /**
+     * Returns the maximum frame rate supported for a given size in a high-speed session.
+     *
+     * This method retrieves the supported high-speed FPS ranges for the given size from the camera
+     * characteristics. It then returns the maximum frame rate (upper bound) among those ranges.
+     *
+     * @param imageFormat The image format. Only [PRIVATE] is supported for high-speed session.
+     * @param size The size for which to find the maximum supported high-speed frame rate.
+     * @return The maximum high-speed frame rate supported for the given size, or 0 if no high-speed
+     *   FPS ranges are supported for that size or the image format is not supported.
+     */
+    public fun getMaxFrameRate(imageFormat: Int, size: Size): Int {
+        if (imageFormat != SUPPORTED_FORMAT) {
+            return 0
+        }
+
+        val supportedFpsRangesForSize =
+            getHighSpeedVideoFpsRangesFor(size).takeIf { it.isNotEmpty() }
+                ?: run {
+                    Logger.w(TAG, "No supported high speed  fps for $size")
+                    return 0
+                }
+
+        return supportedFpsRangesForSize.maxOf { it.upper }
+    }
+
+    /**
+     * Returns size arrangements where all inner lists have the same size, maintaining order.
+     *
+     * This method takes a list of lists of sizes, where each inner list represents the supported
+     * sizes for a specific use case. It finds the common sizes across all use cases and creates
+     * arrangements where each use case has the same size. The order in the first list of the input
+     * determines the order of the common sizes in the output.
+     *
+     * This method is necessary due to a limitation in high-speed session configuration, where all
+     * streams (use cases) in a high-speed session must have the same size.
+     *
+     * @param sizesList A list of lists of sizes. Each inner list represents the supported sizes for
+     *   a use case. The first dimension represents the use case, and the second dimension is the
+     *   supported sizes.
+     * @return A list of size arrangements where each inner list contains the same size. Returns an
+     *   empty list if the input is empty or null.
+     */
+    public fun getSizeArrangements(sizesList: List<List<Size>>): List<List<Size>> {
+        if (sizesList.isEmpty()) {
+            return emptyList()
+        }
+
+        val commonSizes = sizesList.findCommonElements()
+
+        // Generate arrangements with common sizes.
+        return commonSizes.map { commonSize -> List(sizesList.size) { commonSize } }
+    }
+
+    /**
+     * Returns the supported frame rate ranges for high-speed capture sessions with the given
+     * surface sizes.
+     *
+     * High-speed sessions have restrictions:
+     * 1. Maximum 2 surfaces.
+     * 2. All surfaces must have the same size. When the restrictions are not met, this method will
+     *    return null.
+     *
+     * @param surfaceSizes The list of surface sizes.
+     * @return An array of supported frame rate ranges, or null if the input is invalid or no
+     *   supported ranges are found.
+     */
+    public fun getFrameRateRangesFor(surfaceSizes: List<Size>): Array<Range<Int>>? {
+        // High-speed capture sessions have restrictions:
+        // 1. Maximum 2 surfaces.
+        // 2. All surfaces must have the same size.
+        if (surfaceSizes.size !in 1..2 || surfaceSizes.distinct().size != 1) {
+            return null
+        }
+
+        val supportedFpsRanges =
+            getHighSpeedVideoFpsRangesFor(surfaceSizes[0]).takeIf { it.isNotEmpty() } ?: return null
+
+        // For 2 surfaces case, the FPS range must be fixed (lower == upper). See
+        // CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList.
+        return if (surfaceSizes.size == 2) {
+                supportedFpsRanges.filter { it.lower == it.upper }
+            } else {
+                supportedFpsRanges
+            }
+            .toTypedArray()
+    }
+
+    /**
+     * Finds the common elements present in all given lists, preserving the order from the first
+     * list.
+     *
+     * This function takes a list of lists and returns a new list containing only the elements that
+     * appear in every input list. The order of elements in the output list matches their order in
+     * the first list.
+     *
+     * @return A list containing only the elements found in all input lists, ordered according to
+     *   their presence in the first list.
+     */
+    private fun <T> List<List<T>>.findCommonElements(): List<T> {
+        if (isEmpty()) return emptyList()
+
+        val commonElements = this.first().toMutableList()
+        this.drop(1).forEach { commonElements.retainAll(it) }
+        return commonElements
+    }
+
+    private fun getHighSpeedVideoFpsRangesFor(size: Size): List<Range<Int>> {
+        return runCatching {
+                characteristics.streamConfigurationMapCompat.getHighSpeedVideoFpsRangesFor(size)
+            }
+            .getOrNull()
+            ?.filterNotNull()
+            ?.toList() ?: emptyList()
+    }
+
+    private companion object {
+        private const val TAG = "HighSpeedResolver"
+        private const val SUPPORTED_FORMAT = INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/RequestForwardingCaptureCallback.kt b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/RequestForwardingCaptureCallback.kt
new file mode 100644
index 0000000..c304344
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/RequestForwardingCaptureCallback.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CaptureFailure
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import android.os.Build
+import android.view.Surface
+import androidx.annotation.RequiresApi
+
+/**
+ * A `CaptureCallback` that forwards all callbacks to another `CaptureCallback` with a specific
+ * `CaptureRequest`.
+ */
+public class RequestForwardingCaptureCallback(
+    private val forwardedRequest: CaptureRequest,
+    private val delegate: CameraCaptureSession.CaptureCallback
+) : CameraCaptureSession.CaptureCallback() {
+
+    override fun onCaptureStarted(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        timestamp: Long,
+        frameNumber: Long
+    ) {
+        delegate.onCaptureStarted(session, forwardedRequest, timestamp, frameNumber)
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    override fun onReadoutStarted(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        timestamp: Long,
+        frameNumber: Long
+    ) {
+        delegate.onReadoutStarted(session, forwardedRequest, timestamp, frameNumber)
+    }
+
+    override fun onCaptureProgressed(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        partialResult: CaptureResult
+    ) {
+        delegate.onCaptureProgressed(session, forwardedRequest, partialResult)
+    }
+
+    override fun onCaptureCompleted(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        result: TotalCaptureResult
+    ) {
+        delegate.onCaptureCompleted(session, forwardedRequest, result)
+    }
+
+    override fun onCaptureFailed(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        failure: CaptureFailure
+    ) {
+        delegate.onCaptureFailed(session, forwardedRequest, failure)
+    }
+
+    override fun onCaptureSequenceCompleted(
+        session: CameraCaptureSession,
+        sequenceId: Int,
+        frameNumber: Long
+    ) {
+        delegate.onCaptureSequenceCompleted(session, sequenceId, frameNumber)
+    }
+
+    override fun onCaptureSequenceAborted(session: CameraCaptureSession, sequenceId: Int) {
+        delegate.onCaptureSequenceAborted(session, sequenceId)
+    }
+
+    @RequiresApi(Build.VERSION_CODES.N)
+    override fun onCaptureBufferLost(
+        session: CameraCaptureSession,
+        request: CaptureRequest,
+        target: Surface,
+        frameNumber: Long
+    ) {
+        delegate.onCaptureBufferLost(session, forwardedRequest, target, frameNumber)
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index e77663e..4dc8635 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -99,6 +99,7 @@
     private final List<SurfaceCombination> mConcurrentSurfaceCombinations = new ArrayList<>();
     private final List<SurfaceCombination> mPreviewStabilizationSurfaceCombinations =
             new ArrayList<>();
+    private final List<SurfaceCombination> mHighSpeedSurfaceCombinations = new ArrayList<>();
     private final Map<FeatureSettings, List<SurfaceCombination>>
             mFeatureSettingsToSupportedCombinationsMap = new HashMap<>();
     private final List<SurfaceCombination> mSurfaceCombinations10Bit = new ArrayList<>();
@@ -124,6 +125,7 @@
     private final TargetAspectRatio mTargetAspectRatio = new TargetAspectRatio();
     private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
     private final DynamicRangeResolver mDynamicRangeResolver;
+    private final HighSpeedResolver mHighSpeedResolver;
 
     @IntDef({DynamicRange.BIT_DEPTH_8_BIT, DynamicRange.BIT_DEPTH_10_BIT})
     @Retention(RetentionPolicy.SOURCE)
@@ -168,6 +170,7 @@
         }
 
         mDynamicRangeResolver = new DynamicRangeResolver(mCharacteristics);
+        mHighSpeedResolver = new HighSpeedResolver(mCharacteristics);
         generateSupportedCombinationList();
 
         if (mIsUltraHighResolutionSensorSupported) {
@@ -276,6 +279,11 @@
             if (featureSettings.getCameraMode() == CameraMode.DEFAULT) {
                 supportedSurfaceCombinations.addAll(mSurfaceCombinationsUltraHdr);
             }
+        } else if (featureSettings.isHighSpeedOn()) {
+            if (mHighSpeedSurfaceCombinations.isEmpty()) {
+                generateHighSpeedSupportedCombinationList();
+            }
+            supportedSurfaceCombinations.addAll(mHighSpeedSurfaceCombinations);
         } else if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_8_BIT) {
             switch (featureSettings.getCameraMode()) {
                 case CameraMode.CONCURRENT_CAMERA:
@@ -322,6 +330,11 @@
                 getUpdatedSurfaceSizeDefinitionByFormat(imageFormat));
     }
 
+    private int getMaxFrameRate(int imageFormat, @NonNull Size size, boolean isHighSpeedOn) {
+        return isHighSpeedOn ? mHighSpeedResolver.getMaxFrameRate(imageFormat, size)
+                : getMaxFrameRate(mCharacteristics, imageFormat, size);
+    }
+
     static int getMaxFrameRate(CameraCharacteristicsCompat characteristics, int imageFormat,
             Size size) {
         int maxFramerate = 0;
@@ -413,18 +426,16 @@
      *
      * @param targetFrameRate the Target Frame Rate resolved from all current existing surfaces
      *                        and incoming new use cases
+     * @param availableFpsRanges the device available frame rate ranges
      * @return a frame rate range supported by the device that is closest to targetFrameRate
      */
     private @NonNull Range<Integer> getClosestSupportedDeviceFrameRate(
-            @Nullable Range<Integer> targetFrameRate, int maxFps) {
+            @Nullable Range<Integer> targetFrameRate, int maxFps,
+            @Nullable Range<Integer>[] availableFpsRanges) {
         if (targetFrameRate == null || targetFrameRate.equals(FRAME_RATE_RANGE_UNSPECIFIED)) {
             return FRAME_RATE_RANGE_UNSPECIFIED;
         }
 
-        // get all fps ranges supported by device
-        Range<Integer>[] availableFpsRanges =
-                mCharacteristics.get(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
-
         if (availableFpsRanges == null) {
             return FRAME_RATE_RANGE_UNSPECIFIED;
         }
@@ -527,9 +538,11 @@
      * @param currentMaxFps the previously stored Max FPS
      * @param imageFormat   the image format of the incoming surface
      * @param size          the size of the incoming surface
+     * @param isHighSpeedOn whether high-speed session is enabled
      */
-    private int getUpdatedMaximumFps(int currentMaxFps, int imageFormat, Size size) {
-        return Math.min(currentMaxFps, getMaxFrameRate(mCharacteristics, imageFormat, size));
+    private int getUpdatedMaximumFps(int currentMaxFps, int imageFormat, Size size,
+            boolean isHighSpeedOn) {
+        return Math.min(currentMaxFps, getMaxFrameRate(imageFormat, size, isHighSpeedOn));
     }
 
     /**
@@ -557,10 +570,19 @@
             @NonNull List<AttachedSurfaceInfo> attachedSurfaces,
             @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
             boolean isPreviewStabilizationOn,
-            boolean hasVideoCapture) {
+            boolean hasVideoCapture,
+            @Nullable Range<Integer> targetHighSpeedFpsRange) {
         // Refresh Preview Size based on current display configurations.
         refreshPreviewSize();
 
+        boolean isHighSpeedOn = targetHighSpeedFpsRange != null;
+        // Filter out unsupported sizes for high-speed at the beginning to ensure correct
+        // resolution selection later. High-speed session requires all surface sizes to be the same.
+        if (isHighSpeedOn) {
+            newUseCaseConfigsSupportedSizeMap = mHighSpeedResolver.filterCommonSupportedSizes(
+                    newUseCaseConfigsSupportedSizeMap);
+        }
+
         List<UseCaseConfig<?>> newUseCaseConfigs = new ArrayList<>(
                 newUseCaseConfigsSupportedSizeMap.keySet());
 
@@ -572,7 +594,7 @@
 
         boolean isUltraHdrOn = isUltraHdrOn(attachedSurfaces, newUseCaseConfigsSupportedSizeMap);
         FeatureSettings featureSettings = createFeatureSettings(cameraMode, resolvedDynamicRanges,
-                isPreviewStabilizationOn, isUltraHdrOn);
+                isPreviewStabilizationOn, isUltraHdrOn, isHighSpeedOn);
 
         boolean isSurfaceCombinationSupported = isUseCasesCombinationSupported(featureSettings,
                 attachedSurfaces, newUseCaseConfigsSupportedSizeMap);
@@ -586,8 +608,8 @@
         }
 
         // Calculates the target FPS range
-        Range<Integer> targetFpsRange = getTargetFpsRange(attachedSurfaces,
-                newUseCaseConfigs, useCasesPriorityOrder);
+        Range<Integer> targetFpsRange = isHighSpeedOn ? targetHighSpeedFpsRange
+                : getTargetFpsRange(attachedSurfaces, newUseCaseConfigs, useCasesPriorityOrder);
         // Filters the unnecessary output sizes for performance improvement. This will
         // significantly reduce the number of all possible size arrangements below.
         Map<UseCaseConfig<?>, List<Size>> useCaseConfigToFilteredSupportedSizesMap =
@@ -608,8 +630,8 @@
 
         // Get all possible size arrangements
         List<List<Size>> allPossibleSizeArrangements =
-                getAllPossibleSizeArrangements(
-                        supportedOutputSizesList);
+                isHighSpeedOn ? mHighSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+                        : getAllPossibleSizeArrangements(supportedOutputSizesList);
 
         Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
         Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
@@ -632,13 +654,15 @@
         boolean containsZsl = StreamUseCaseUtil.containsZslUseCase(attachedSurfaces,
                 newUseCaseConfigs);
         List<SurfaceConfig> orderedSurfaceConfigListForStreamUseCase = null;
-        int maxSupportedFps = getMaxSupportedFpsFromAttachedSurfaces(attachedSurfaces);
+        int maxSupportedFps = getMaxSupportedFpsFromAttachedSurfaces(attachedSurfaces,
+                isHighSpeedOn);
         // Only checks the stream use case combination support when ZSL is not required.
         if (mIsStreamUseCaseSupported && !containsZsl) {
             // Check if any possible size arrangement is supported for stream use case.
             for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
                 List<SurfaceConfig> surfaceConfigs = getSurfaceConfigListAndFpsCeiling(
                         cameraMode,
+                        isHighSpeedOn,
                         attachedSurfaces, possibleSizeList, newUseCaseConfigs,
                         useCasesPriorityOrder, maxSupportedFps,
                         surfaceConfigIndexAttachedSurfaceInfoMap,
@@ -684,7 +708,7 @@
         for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
             // Attach SurfaceConfig of original use cases since it will impact the new use cases
             Pair<List<SurfaceConfig>, Integer> resultPair =
-                    getSurfaceConfigListAndFpsCeiling(cameraMode,
+                    getSurfaceConfigListAndFpsCeiling(cameraMode, isHighSpeedOn,
                             attachedSurfaces, possibleSizeList, newUseCaseConfigs,
                             useCasesPriorityOrder, maxSupportedFps, null, null);
             List<SurfaceConfig> surfaceConfigList = resultPair.first;
@@ -758,9 +782,11 @@
         if (savedSizes != null) {
             Range<Integer> targetFramerateForDevice = null;
             if (targetFpsRange != null) {
-                targetFramerateForDevice =
-                        getClosestSupportedDeviceFrameRate(targetFpsRange,
-                                savedConfigMaxFps);
+                Range<Integer>[] availableFpsRanges = isHighSpeedOn
+                        ? mHighSpeedResolver.getFrameRateRangesFor(savedSizes)
+                        : mCharacteristics.get(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+                targetFramerateForDevice = getClosestSupportedDeviceFrameRate(targetFpsRange,
+                                savedConfigMaxFps, availableFpsRanges);
             }
             for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
                 Size resolutionForUseCase = savedSizes.get(
@@ -843,7 +869,7 @@
     private @NonNull FeatureSettings createFeatureSettings(
             @CameraMode.Mode int cameraMode,
             @NonNull Map<UseCaseConfig<?>, DynamicRange> resolvedDynamicRanges,
-            boolean isPreviewStabilizationOn, boolean isUltraHdrOn) {
+            boolean isPreviewStabilizationOn, boolean isUltraHdrOn, boolean isHighSpeedOn) {
         int requiredMaxBitDepth = getRequiredMaxBitDepth(resolvedDynamicRanges);
 
         if (cameraMode != CameraMode.DEFAULT && isUltraHdrOn) {
@@ -861,8 +887,13 @@
                     CameraMode.toLabelString(cameraMode)));
         }
 
+        if (isHighSpeedOn && !mHighSpeedResolver.isHighSpeedSupported()) {
+            throw new IllegalArgumentException(
+                    "High-speed session is not supported on this device.");
+        }
+
         return FeatureSettings.of(cameraMode, requiredMaxBitDepth, isPreviewStabilizationOn,
-                isUltraHdrOn);
+                isUltraHdrOn, isHighSpeedOn);
     }
 
     /**
@@ -943,14 +974,15 @@
     }
 
     private int getMaxSupportedFpsFromAttachedSurfaces(
-            @NonNull List<AttachedSurfaceInfo> attachedSurfaces) {
+            @NonNull List<AttachedSurfaceInfo> attachedSurfaces, boolean isHighSpeedOn) {
         int existingSurfaceFrameRateCeiling = Integer.MAX_VALUE;
 
         for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
             //get the fps ceiling for existing surfaces
             existingSurfaceFrameRateCeiling = getUpdatedMaximumFps(
                     existingSurfaceFrameRateCeiling,
-                    attachedSurfaceInfo.getImageFormat(), attachedSurfaceInfo.getSize());
+                    attachedSurfaceInfo.getImageFormat(), attachedSurfaceInfo.getSize(),
+                    isHighSpeedOn);
         }
 
         return existingSurfaceFrameRateCeiling;
@@ -981,7 +1013,8 @@
                 int maxFrameRate = Integer.MAX_VALUE;
                 // Filters the sizes with frame rate only if there is target FPS setting
                 if (targetFpsRange != null) {
-                    maxFrameRate = getMaxFrameRate(mCharacteristics, imageFormat, size);
+                    maxFrameRate = getMaxFrameRate(imageFormat, size,
+                            featureSettings.isHighSpeedOn());
                 }
                 Set<Integer> uniqueMaxFrameRates = configSizeUniqueMaxFpsMap.get(configSize);
                 // Creates an empty FPS list for the config size when it doesn't exist.
@@ -1027,6 +1060,7 @@
 
     private Pair<List<SurfaceConfig>, Integer> getSurfaceConfigListAndFpsCeiling(
             @CameraMode.Mode int cameraMode,
+            boolean isHighSpeedOn,
             List<AttachedSurfaceInfo> attachedSurfaces,
             List<Size> possibleSizeList, List<UseCaseConfig<?>> newUseCaseConfigs,
             List<Integer> useCasesPriorityOrder,
@@ -1063,7 +1097,7 @@
             currentConfigFramerateCeiling = getUpdatedMaximumFps(
                     currentConfigFramerateCeiling,
                     newUseCase.getInputFormat(),
-                    size);
+                    size, isHighSpeedOn);
         }
         return new Pair<>(surfaceConfigList, currentConfigFramerateCeiling);
     }
@@ -1319,6 +1353,21 @@
         }
     }
 
+    private void generateHighSpeedSupportedCombinationList() {
+        if (!mHighSpeedResolver.isHighSpeedSupported()) {
+            return;
+        }
+        mHighSpeedSurfaceCombinations.clear();
+        // Find maximum supported size.
+        Size maxSize = mHighSpeedResolver.getMaxSize();
+        if (maxSize != null) {
+            mHighSpeedSurfaceCombinations.addAll(
+                    GuaranteedConfigurationsUtil.generateHighSpeedSupportedCombinationList(maxSize,
+                            getUpdatedSurfaceSizeDefinitionByFormat(
+                                    ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE)));
+        }
+    }
+
     private void checkCustomization() {
         // TODO(b/119466260): Integrate found feasible stream combinations into supported list
     }
@@ -1540,9 +1589,9 @@
     abstract static class FeatureSettings {
         static @NonNull FeatureSettings of(@CameraMode.Mode int cameraMode,
                 @RequiredMaxBitDepth int requiredMaxBitDepth, boolean isPreviewStabilizationOn,
-                boolean isUltraHdrOn) {
+                boolean isUltraHdrOn, boolean isHighSpeedOn) {
             return new AutoValue_SupportedSurfaceCombination_FeatureSettings(cameraMode,
-                    requiredMaxBitDepth, isPreviewStabilizationOn, isUltraHdrOn);
+                    requiredMaxBitDepth, isPreviewStabilizationOn, isUltraHdrOn, isHighSpeedOn);
         }
 
         /**
@@ -1580,5 +1629,10 @@
          * Whether the Ultra HDR image capture is enabled.
          */
         abstract boolean isUltraHdrOn();
+
+        /**
+         * Whether the high-speed capture is enabled.
+         */
+        abstract boolean isHighSpeedOn();
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
index faab52d..9993a25 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSession.java
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.SessionConfiguration;
@@ -182,6 +183,16 @@
             throws CameraAccessException;
 
     /**
+     * Create a unmodifiable list of requests that is suitable for constrained high speed capture
+     * session streaming.
+     *
+     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList(CaptureRequest)
+     */
+    @NonNull
+    List<CaptureRequest> createHighSpeedRequestList(@NonNull CaptureRequest request)
+            throws CameraAccessException;
+
+    /**
      * Submit a request for an image to be captured by the camera device.
      *
      * <p>The behavior of this method matches that of
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
index df7283c..1e43fc9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionBaseImpl.java
@@ -16,8 +16,11 @@
 
 package androidx.camera.camera2.internal;
 
+import static java.util.Collections.emptyList;
+
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.os.Build;
@@ -398,6 +401,21 @@
     }
 
     @Override
+    @NonNull
+    public List<CaptureRequest> createHighSpeedRequestList(@NonNull CaptureRequest request)
+            throws CameraAccessException {
+        CameraCaptureSession cameraCaptureSession =
+                Preconditions.checkNotNull(mCameraCaptureSessionCompat).toCameraCaptureSession();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                && cameraCaptureSession instanceof CameraConstrainedHighSpeedCaptureSession) {
+            return Api23Impl.createHighSpeedRequestList(
+                    (CameraConstrainedHighSpeedCaptureSession) cameraCaptureSession, request);
+        } else {
+            return emptyList();
+        }
+    }
+
+    @Override
     public int captureSingleRequest(@NonNull CaptureRequest request, @NonNull Executor executor,
             CameraCaptureSession.@NonNull CaptureCallback listener) throws CameraAccessException {
         Preconditions.checkNotNull(mCameraCaptureSessionCompat,
@@ -622,5 +640,13 @@
         static Surface getInputSurface(CameraCaptureSession cameraCaptureSession) {
             return cameraCaptureSession.getInputSurface();
         }
+
+        @NonNull
+        static List<CaptureRequest> createHighSpeedRequestList(
+                @NonNull CameraConstrainedHighSpeedCaptureSession constrainedHighSpeedSession,
+                @NonNull CaptureRequest captureRequest)
+                throws CameraAccessException {
+            return constrainedHighSpeedSession.createHighSpeedRequestList(captureRequest);
+        }
     }
 }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/HighSpeedResolverTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/HighSpeedResolverTest.kt
new file mode 100644
index 0000000..5163753
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/HighSpeedResolverTest.kt
@@ -0,0 +1,304 @@
+/*
+ * 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
+
+import android.graphics.ImageFormat
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.utils.CompareSizesByArea
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_720P
+import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_2160P
+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.mockito.Mockito
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraCharacteristics
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class HighSpeedResolverTest {
+
+    private companion object {
+        private const val CAMERA_ID = "0"
+
+        private const val FPS_30 = 30
+        private const val FPS_120 = 120
+        private const val FPS_240 = 240
+        private const val FPS_480 = 480
+
+        private val RANGE_30_120 = Range.create(FPS_30, FPS_120)
+        private val RANGE_120_120 = Range.create(FPS_120, FPS_120)
+        private val RANGE_30_240 = Range.create(FPS_30, FPS_240)
+        private val RANGE_240_240 = Range.create(FPS_240, FPS_240)
+        private val RANGE_30_480 = Range.create(FPS_30, FPS_480)
+        private val RANGE_480_480 = Range.create(FPS_480, FPS_480)
+
+        private const val FORMAT_PRIVATE =
+            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+
+        private val COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP =
+            mapOf(
+                RESOLUTION_1080P to
+                    listOf(
+                        RANGE_30_120,
+                        RANGE_120_120,
+                        RANGE_30_240,
+                        RANGE_240_240,
+                    ),
+                RESOLUTION_720P to
+                    listOf(
+                        RANGE_30_120,
+                        RANGE_120_120,
+                        RANGE_30_240,
+                        RANGE_240_240,
+                        RANGE_30_480,
+                        RANGE_480_480
+                    )
+            )
+    }
+
+    private val defaultHighSpeedResolver = createHighSpeedResolver()
+    private val emptyHighSpeedResolver =
+        createHighSpeedResolver(createCharacteristics(supportedHighSpeedSizeAndFpsMap = emptyMap()))
+
+    @Test
+    fun filterCommonSupportedSizes_returnsCorrectMap() {
+        val useCaseSupportedSizeMap =
+            listOf(
+                    listOf(RESOLUTION_480P, RESOLUTION_720P, RESOLUTION_1080P),
+                    listOf(RESOLUTION_1080P, RESOLUTION_720P, RESOLUTION_2160P),
+                    listOf(RESOLUTION_480P, RESOLUTION_720P, RESOLUTION_1080P, RESOLUTION_VGA)
+                )
+                .toUseCaseSupportedSizeMap()
+
+        val result = defaultHighSpeedResolver.filterCommonSupportedSizes(useCaseSupportedSizeMap)
+
+        // Assert: return common sizes and preserve the original order.
+        assertThat(result.values)
+            .containsExactly(
+                listOf(RESOLUTION_720P, RESOLUTION_1080P),
+                listOf(RESOLUTION_1080P, RESOLUTION_720P),
+                listOf(RESOLUTION_720P, RESOLUTION_1080P)
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun getMaxSize_noSupportedSizes_returnsNull() {
+        val result = emptyHighSpeedResolver.maxSize
+
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun getMaxSize_supportedSizesExist_returnsLargestSize() {
+        val result = defaultHighSpeedResolver.maxSize
+
+        assertThat(result).isEqualTo(RESOLUTION_1080P)
+    }
+
+    @Test
+    fun getMaxFrameRate_unsupportedImageFormat_returnsZero() {
+        val result = defaultHighSpeedResolver.getMaxFrameRate(ImageFormat.JPEG, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(0)
+    }
+
+    @Test
+    fun getMaxFrameRate_noSupportedFpsRanges_returnsZero() {
+        val result = emptyHighSpeedResolver.getMaxFrameRate(FORMAT_PRIVATE, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(0)
+    }
+
+    @Test
+    fun getMaxFrameRate_supportedFpsRangesExist_returnMaxFps() {
+        val result = defaultHighSpeedResolver.getMaxFrameRate(FORMAT_PRIVATE, RESOLUTION_1080P)
+
+        assertThat(result).isEqualTo(FPS_240)
+    }
+
+    @Test
+    fun getSizeArrangements_emptyInput_returnEmptyList() {
+        val sizeArrangements = defaultHighSpeedResolver.getSizeArrangements(emptyList())
+
+        assertThat(sizeArrangements).isEmpty()
+    }
+
+    @Test
+    fun getSizeArrangements_hasCommonSizes_returnCorrectArrangements() {
+        val common1080p = RESOLUTION_1080P
+        val common720p = RESOLUTION_720P
+        val supportedOutputSizesList =
+            listOf(
+                listOf(RESOLUTION_480P, common720p, common1080p),
+                listOf(common1080p, common720p, RESOLUTION_2160P),
+                listOf(RESOLUTION_480P, common720p, common1080p, RESOLUTION_VGA)
+            )
+        val sizeArrangements =
+            defaultHighSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+
+        assertThat(sizeArrangements)
+            .containsExactly(
+                listOf(common720p, common720p, common720p),
+                listOf(common1080p, common1080p, common1080p)
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun getSizeArrangements_noCommonSizes_returnEmptyList() {
+        val supportedOutputSizesList =
+            listOf(
+                listOf(RESOLUTION_480P, RESOLUTION_720P),
+                listOf(RESOLUTION_1080P, RESOLUTION_2160P),
+                listOf(RESOLUTION_1080P, RESOLUTION_720P)
+            )
+
+        val result = defaultHighSpeedResolver.getSizeArrangements(supportedOutputSizesList)
+
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_invalidInput_returnsNull() {
+        // More than 2 surfaces.
+        assertThat(
+                defaultHighSpeedResolver.getFrameRateRangesFor(
+                    listOf(RESOLUTION_720P, RESOLUTION_720P, RESOLUTION_720P)
+                )
+            )
+            .isNull()
+
+        // Different sizes.
+        assertThat(
+                defaultHighSpeedResolver.getFrameRateRangesFor(
+                    listOf(RESOLUTION_720P, RESOLUTION_1080P)
+                )
+            )
+            .isNull()
+
+        // Empty list.
+        assertThat(defaultHighSpeedResolver.getFrameRateRangesFor(emptyList())).isNull()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_noSupportedSizes_returnsNull() {
+        assertThat(emptyHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P))).isNull()
+    }
+
+    @Test
+    fun getFrameRateRangesFor_oneSurface_returnsAllSupportedRanges() {
+        val result = defaultHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P))
+
+        assertThat(result!!.toList())
+            .containsExactly(
+                RANGE_30_120,
+                RANGE_120_120,
+                RANGE_30_240,
+                RANGE_240_240,
+                RANGE_30_480,
+                RANGE_480_480
+            )
+    }
+
+    @Test
+    fun getFrameRateRangesFor_twoSurfaces_returnsFixedFpsRanges() {
+        val result =
+            defaultHighSpeedResolver.getFrameRateRangesFor(listOf(RESOLUTION_720P, RESOLUTION_720P))
+
+        assertThat(result!!.toList())
+            .containsExactly(RANGE_120_120, RANGE_240_240, RANGE_480_480)
+            .inOrder()
+    }
+
+    private fun createHighSpeedResolver(
+        characteristics: CameraCharacteristicsCompat = createCharacteristics(),
+    ): HighSpeedResolver {
+        return HighSpeedResolver(characteristics = characteristics)
+    }
+
+    private fun createCharacteristics(
+        cameraId: String = CAMERA_ID,
+        hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? =
+            COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+    ): CameraCharacteristicsCompat {
+        val mockMap =
+            Mockito.mock(StreamConfigurationMap::class.java).also { map ->
+                if (supportedHighSpeedSizeAndFpsMap != null) {
+                    // Mock highSpeedVideoSizes
+                    Mockito.`when`(map.highSpeedVideoSizes)
+                        .thenReturn(supportedHighSpeedSizeAndFpsMap.keys.toTypedArray())
+
+                    // Mock highSpeedVideoFpsRanges
+                    val allFpsRanges = supportedHighSpeedSizeAndFpsMap.values.flatten().distinct()
+                    Mockito.`when`(map.highSpeedVideoFpsRanges)
+                        .thenReturn(allFpsRanges.toTypedArray())
+
+                    // Mock getHighSpeedVideoSizesFor
+                    allFpsRanges.forEach { fpsRange ->
+                        val sizesForRange =
+                            supportedHighSpeedSizeAndFpsMap.entries
+                                .filter { (_, fpsRanges) -> fpsRanges.contains(fpsRange) }
+                                .map { it.key }
+                                .sortedWith(CompareSizesByArea(false)) // Descending order
+                                .toTypedArray()
+                        Mockito.`when`(map.getHighSpeedVideoSizesFor(fpsRange))
+                            .thenReturn(sizesForRange)
+                    }
+
+                    // Mock getHighSpeedVideoFpsRangesFor
+                    supportedHighSpeedSizeAndFpsMap.forEach { (size, fpsRanges) ->
+                        Mockito.`when`(map.getHighSpeedVideoFpsRangesFor(size))
+                            .thenReturn(fpsRanges.toTypedArray())
+                    }
+                }
+            }
+
+        val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+        Shadow.extract<ShadowCameraCharacteristics>(characteristics).apply {
+            set(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, hardwareLevel)
+            set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
+        }
+
+        return CameraCharacteristicsCompat.toCameraCharacteristicsCompat(characteristics, cameraId)
+    }
+
+    private fun List<List<Size>>.toUseCaseSupportedSizeMap(): Map<UseCaseConfig<*>, List<Size>> {
+        return associate { sizes ->
+            FakeUseCaseConfig.Builder(CaptureType.PREVIEW, FORMAT_PRIVATE).build().currentConfig to
+                sizes
+        }
+    }
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index d09a141..f9d9cba 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -240,7 +240,8 @@
                 CameraMode.CONCURRENT_CAMERA,
                 BIT_DEPTH_8_BIT,
                 /*isPreviewStabilizationOn=*/false,
-                /*isUltraHdrOn=*/ false
+                /*isUltraHdrOn=*/ false,
+                /*isHighSpeedOn=*/ false
         );
         assertFalse(shouldUseStreamUseCase(featureSettings));
     }
@@ -251,7 +252,8 @@
                 CameraMode.DEFAULT,
                 BIT_DEPTH_10_BIT,
                 /*isPreviewStabilizationOn=*/false,
-                /*isUltraHdrOn=*/ false
+                /*isUltraHdrOn=*/ false,
+                /*isHighSpeedOn=*/ false
         );
         assertFalse(shouldUseStreamUseCase(featureSettings));
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index aa25062..cf9f36b 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -25,6 +25,7 @@
 import android.hardware.camera2.CameraManager
 import android.hardware.camera2.CameraMetadata
 import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
 import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
 import android.hardware.camera2.params.DynamicRangeProfiles
 import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
@@ -69,12 +70,14 @@
 import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
 import androidx.camera.core.impl.ImageInputConfig
 import androidx.camera.core.impl.StreamSpec
+import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.SurfaceConfig.ConfigSize
 import androidx.camera.core.impl.SurfaceConfig.ConfigType
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.utils.CompareSizesByArea
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1440P
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_720P
@@ -149,6 +152,25 @@
     arrayOf(
         Size(8000, 6000), // 4:3
     )
+private val COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP =
+    mapOf(
+        RESOLUTION_1080P to
+            listOf(
+                Range.create(30, 120),
+                Range.create(120, 120),
+                Range.create(30, 240),
+                Range.create(240, 240),
+            ),
+        RESOLUTION_720P to
+            listOf(
+                Range.create(30, 120),
+                Range.create(120, 120),
+                Range.create(30, 240),
+                Range.create(240, 240),
+                Range.create(30, 480),
+                Range.create(480, 480)
+            )
+    )
 
 /** Robolectric test for [SupportedSurfaceCombination] class */
 @RunWith(RobolectricTestRunner::class)
@@ -598,6 +620,68 @@
         }
     }
 
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun checkSurfaceCombinationSupportForHighSpeed() {
+        setupCameraAndInitCameraX(
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP
+        )
+        val supportedSurfaceCombination =
+            SupportedSurfaceCombination(
+                context,
+                DEFAULT_CAMERA_ID,
+                cameraManagerCompat!!,
+                mockCamcorderProfileHelper
+            )
+
+        // The expected SurfaceConfig is PRIV + RECORD because the max high speed size 1920x1080 is
+        // between PREVIEW and RECORD size.
+        val shouldSupportCombinations =
+            listOf(
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                }
+            )
+        shouldSupportCombinations.forEach {
+            assertThat(
+                    supportedSurfaceCombination.checkSupported(
+                        createFeatureSettings(isHighSpeedOn = true),
+                        it.surfaceConfigList
+                    )
+                )
+                .isTrue()
+        }
+
+        val shouldNotSupportCombinations =
+            listOf(
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM))
+                },
+                SurfaceCombination().apply {
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                    addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW))
+                }
+            )
+        shouldNotSupportCombinations.forEach {
+            assertThat(
+                    supportedSurfaceCombination.checkSupported(
+                        createFeatureSettings(isHighSpeedOn = true),
+                        it.surfaceConfigList
+                    )
+                )
+                .isFalse()
+        }
+    }
+
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Surface config transformation tests
@@ -1601,6 +1685,7 @@
 
     private fun getSuggestedSpecsAndVerify(
         useCasesExpectedSizeMap: Map<UseCase, Size>,
+        useCasesOutputSizesMap: Map<UseCase, List<Size>>? = null,
         attachedSurfaceInfoList: List<AttachedSurfaceInfo> = emptyList(),
         hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
         capabilities: IntArray? = null,
@@ -1611,8 +1696,10 @@
         default10BitProfile: Long? = null,
         useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap(),
         supportedOutputFormats: IntArray? = null,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? = null,
         isPreviewStabilizationOn: Boolean = false,
-        hasVideoCapture: Boolean = false
+        hasVideoCapture: Boolean = false,
+        targetHighSpeedFpsRange: Range<Int>? = null,
     ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
         setupCameraAndInitCameraX(
             hardwareLevel = hardwareLevel,
@@ -1620,6 +1707,7 @@
             dynamicRangeProfiles = dynamicRangeProfiles,
             default10BitProfile = default10BitProfile,
             supportedFormats = supportedOutputFormats,
+            supportedHighSpeedSizeAndFpsMap = supportedHighSpeedSizeAndFpsMap,
         )
         val supportedSurfaceCombination =
             SupportedSurfaceCombination(
@@ -1628,17 +1716,19 @@
                 cameraManagerCompat!!,
                 mockCamcorderProfileHelper
             )
-
         val useCaseConfigMap = getUseCaseToConfigMap(useCasesExpectedSizeMap.keys.toList())
         val useCaseConfigToOutputSizesMap =
-            getUseCaseConfigToOutputSizesMap(useCaseConfigMap.values.toList())
+            useCaseConfigMap.entries.associate { (useCase, config) ->
+                config to (useCasesOutputSizesMap?.get(useCase) ?: DEFAULT_SUPPORTED_SIZES.toList())
+            }
         val resultPair =
             supportedSurfaceCombination.getSuggestedStreamSpecifications(
                 cameraMode,
                 attachedSurfaceInfoList,
                 useCaseConfigToOutputSizesMap,
                 isPreviewStabilizationOn,
-                hasVideoCapture
+                hasVideoCapture,
+                targetHighSpeedFpsRange
             )
         val suggestedStreamSpecsForNewUseCases = resultPair.first
         val suggestedStreamSpecsForOldSurfaces = resultPair.second
@@ -1723,17 +1813,6 @@
         return useCaseConfigMap
     }
 
-    private fun getUseCaseConfigToOutputSizesMap(
-        useCaseConfigs: List<UseCaseConfig<*>>
-    ): Map<UseCaseConfig<*>, List<Size>> {
-        val resultMap =
-            mutableMapOf<UseCaseConfig<*>, List<Size>>().apply {
-                useCaseConfigs.forEach { put(it, DEFAULT_SUPPORTED_SIZES.toList()) }
-            }
-
-        return resultMap
-    }
-
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Resolution selection tests for Ultra HDR
@@ -3308,6 +3387,137 @@
             .isFalse()
     }
 
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_returnsCorrectSizeAndFpsRange() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW, surfaceOccupancyPriority = 2)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE, surfaceOccupancyPriority = 5)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_VGA, RESOLUTION_1080P, RESOLUTION_720P),
+                videoUseCase to listOf(RESOLUTION_1440P, RESOLUTION_720P, RESOLUTION_1080P)
+            )
+        // videoUseCase has higher surface priority so the expected size should be the first
+        // common size of videoUseCases. i.e. RESOLUTION_720P.
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_720P, videoUseCase to RESOLUTION_720P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+            targetHighSpeedFpsRange = Range.create(240, 240),
+            compareExpectedFps = Range.create(240, 240)
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_singleSurface_returnsCorrectSizeAndClosestFps() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val useCasesOutputSizesMap = mapOf(previewUseCase to listOf(RESOLUTION_1080P))
+        val useCaseExpectedResultMap = mapOf(previewUseCase to RESOLUTION_1080P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+            targetHighSpeedFpsRange = Range.create(30, 480),
+            compareExpectedFps = Range.create(30, 240) // Find the closest supported fps.
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_multipleSurfaces_returnsCorrectSizeAndClosetMaxFps() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_1080P),
+                videoUseCase to listOf(RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_1080P, videoUseCase to RESOLUTION_1080P)
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            capabilities = intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+            useCasesOutputSizesMap = useCasesOutputSizesMap,
+            supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+            targetHighSpeedFpsRange = Range.create(30, 480),
+            compareExpectedFps = Range.create(240, 240) // Find the closest max supported fps.
+        )
+    }
+
+    @Config(minSdk = 21, maxSdk = 22)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_unsupportedSdkVersion_throwException() {
+        val useCase = createUseCase(CaptureType.PREVIEW)
+        val useCaseExpectedResultMap = mapOf(useCase to RESOLUTION_1080P)
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_noCommonSize_throwException() {
+        val previewUseCase = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase to listOf(RESOLUTION_VGA, RESOLUTION_720P),
+                videoUseCase to listOf(RESOLUTION_1440P, RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(previewUseCase to RESOLUTION_VGA, videoUseCase to RESOLUTION_1440P)
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                useCasesOutputSizesMap = useCasesOutputSizesMap,
+                supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSuggestedStreamSpec_highSpeed_tooManyUseCases_throwException() {
+        val previewUseCase1 = createUseCase(CaptureType.PREVIEW)
+        val previewUseCase2 = createUseCase(CaptureType.PREVIEW)
+        val videoUseCase = createUseCase(CaptureType.VIDEO_CAPTURE)
+        val useCasesOutputSizesMap =
+            mapOf(
+                previewUseCase1 to listOf(RESOLUTION_1080P),
+                previewUseCase2 to listOf(RESOLUTION_1080P),
+                videoUseCase to listOf(RESOLUTION_1080P)
+            )
+        val useCaseExpectedResultMap =
+            mapOf(
+                previewUseCase1 to RESOLUTION_1080P,
+                previewUseCase2 to RESOLUTION_1080P,
+                videoUseCase to RESOLUTION_1080P
+            )
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                    intArrayOf(REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO),
+                useCasesOutputSizesMap = useCasesOutputSizesMap,
+                supportedHighSpeedSizeAndFpsMap = COMMON_HIGH_SPEED_SUPPORTED_SIZE_FPS_MAP,
+                targetHighSpeedFpsRange = Range.create(240, 240),
+            )
+        }
+    }
+
     /**
      * Sets up camera according to the specified settings and initialize [CameraX].
      *
@@ -3322,6 +3532,8 @@
      *   [DEFAULT_SUPPORTED_SIZES].
      * @param supportedHighResolutionSizes the high resolution supported sizes of the camera.
      *   Default value is null.
+     * @param supportedHighSpeedSizeAndFpsMap a map of supported high speed video sizes to their
+     *   corresponding lists of supported FPS ranges. Default value is null.
      * @param maximumResolutionSupportedSizes the maximum resolution mode supported sizes of the
      *   camera. Default value is null.
      * @param maximumResolutionHighResolutionSupportedSizes the maximum resolution mode high
@@ -3336,6 +3548,7 @@
         supportedSizes: Array<Size>? = DEFAULT_SUPPORTED_SIZES,
         supportedFormats: IntArray? = null,
         supportedHighResolutionSizes: Array<Size>? = null,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? = null,
         maximumResolutionSupportedSizes: Array<Size>? = null,
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
@@ -3350,6 +3563,7 @@
             supportedSizes,
             supportedFormats,
             supportedHighResolutionSizes,
+            supportedHighSpeedSizeAndFpsMap,
             maximumResolutionSupportedSizes,
             maximumResolutionHighResolutionSupportedSizes,
             dynamicRangeProfiles,
@@ -3400,6 +3614,8 @@
      * @param supportedFormats the supported output formats of the camera. Default value is null.
      * @param supportedHighResolutionSizes the high resolution supported sizes of the camera.
      *   Default value is null.
+     * @param supportedHighSpeedSizeAndFpsMap a map of supported high speed video sizes to their
+     *   corresponding lists of supported FPS ranges. Default value is null.
      * @param maximumResolutionSupportedSizes the maximum resolution mode supported sizes of the
      *   camera. Default value is null.
      * @param maximumResolutionHighResolutionSupportedSizes the maximum resolution mode high
@@ -3414,6 +3630,7 @@
         supportedSizes: Array<Size>? = DEFAULT_SUPPORTED_SIZES,
         supportedFormats: IntArray? = null,
         supportedHighResolutionSizes: Array<Size>? = null,
+        supportedHighSpeedSizeAndFpsMap: Map<Size, List<Range<Int>>>? = null,
         maximumResolutionSupportedSizes: Array<Size>? = null,
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
@@ -3515,6 +3732,38 @@
                     Mockito.`when`(map.getHighResolutionOutputSizes(ArgumentMatchers.anyInt()))
                         .thenReturn(supportedHighResolutionSizes)
                 }
+
+                if (
+                    supportedHighSpeedSizeAndFpsMap != null &&
+                        Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+                ) {
+                    // Mock highSpeedVideoSizes
+                    Mockito.`when`(map.highSpeedVideoSizes)
+                        .thenReturn(supportedHighSpeedSizeAndFpsMap.keys.toTypedArray())
+
+                    // Mock highSpeedVideoFpsRanges
+                    val allFpsRanges = supportedHighSpeedSizeAndFpsMap.values.flatten().distinct()
+                    Mockito.`when`(map.highSpeedVideoFpsRanges)
+                        .thenReturn(allFpsRanges.toTypedArray())
+
+                    // Mock getHighSpeedVideoSizesFor
+                    allFpsRanges.forEach { fpsRange ->
+                        val sizesForRange =
+                            supportedHighSpeedSizeAndFpsMap.entries
+                                .filter { (_, fpsRanges) -> fpsRanges.contains(fpsRange) }
+                                .map { it.key }
+                                .sortedWith(CompareSizesByArea(false)) // Descending order
+                                .toTypedArray()
+                        Mockito.`when`(map.getHighSpeedVideoSizesFor(fpsRange))
+                            .thenReturn(sizesForRange)
+                    }
+
+                    // Mock getHighSpeedVideoFpsRangesFor
+                    supportedHighSpeedSizeAndFpsMap.forEach { (size, fpsRanges) ->
+                        Mockito.`when`(map.getHighSpeedVideoFpsRangesFor(size))
+                            .thenReturn(fpsRanges.toTypedArray())
+                    }
+                }
             }
 
         val maximumResolutionMap =
@@ -3651,9 +3900,16 @@
     private fun createUseCase(
         captureType: CaptureType,
         targetFrameRate: Range<Int>? = null,
-        dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED
+        dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED,
+        surfaceOccupancyPriority: Int? = null
     ): UseCase {
-        return createUseCase(captureType, targetFrameRate, dynamicRange, false)
+        return createUseCase(
+            captureType,
+            targetFrameRate,
+            dynamicRange,
+            streamUseCaseOverride = false,
+            surfaceOccupancyPriority = surfaceOccupancyPriority
+        )
     }
 
     private fun createUseCase(
@@ -3661,7 +3917,8 @@
         targetFrameRate: Range<Int>? = null,
         dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED,
         streamUseCaseOverride: Boolean = false,
-        imageFormat: Int? = null
+        imageFormat: Int? = null,
+        surfaceOccupancyPriority: Int? = null,
     ): UseCase {
         val builder =
             FakeUseCaseConfig.Builder(
@@ -3691,6 +3948,8 @@
             )
         }
 
+        surfaceOccupancyPriority?.let { builder.setSurfaceOccupancyPriority(it) }
+
         return builder.build()
     }
 
@@ -3708,12 +3967,14 @@
         @RequiredMaxBitDepth requiredMaxBitDepth: Int = BIT_DEPTH_8_BIT,
         isPreviewStabilizationOn: Boolean = false,
         isUltraHdrOn: Boolean = false,
+        isHighSpeedOn: Boolean = false,
     ): FeatureSettings {
         return FeatureSettings.of(
             cameraMode,
             requiredMaxBitDepth,
             isPreviewStabilizationOn,
-            isUltraHdrOn
+            isUltraHdrOn,
+            isHighSpeedOn
         )
     }
 }
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 4f331a5..fe9cc3c 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -48,6 +48,7 @@
   }
 
   public final class BasicTooltipKt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
@@ -63,7 +64,7 @@
   }
 
   public final class BasicTooltip_androidKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
   public final class BorderKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 1f847d1..db4b26e 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -48,6 +48,7 @@
   }
 
   public final class BasicTooltipKt {
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
@@ -63,7 +64,7 @@
   }
 
   public final class BasicTooltip_androidKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
   public final class BorderKt {
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerBenchmark.kt
index e183e97..7828cd4 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerBenchmark.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.benchmark
 
+import androidx.compose.foundation.benchmark.lazy.doFramesUntilIdle
+import androidx.compose.testutils.ComposeExecutionControl
 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
 import androidx.compose.testutils.benchmark.benchmarkDrawPerf
 import androidx.compose.testutils.benchmark.benchmarkFirstCompose
@@ -27,6 +29,7 @@
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import org.junit.Rule
@@ -84,6 +87,45 @@
     }
 
     @Test
+    fun mouseWheelScroll_initialScroll() {
+        with(benchmarkRule) {
+            runBenchmarkFor({ MouseWheelScrollerTestCase() }) {
+                measureRepeatedOnUiThread {
+                    runWithTimingDisabled {
+                        doFrame()
+                        assertNoPendingRecompositionMeasureOrLayout()
+                    }
+
+                    performToggle(getTestCase())
+
+                    runWithTimingDisabled {
+                        // This benchmark only cares about initial cost of adding the scroll node
+                        disposeContent()
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun mouseWheelScroll_successiveScrolls() {
+        with(benchmarkRule) {
+            runBenchmarkFor({ MouseWheelScrollerTestCase() }) {
+                runOnUiThread {
+                    doFrame()
+                    performToggle(getTestCase())
+                    doFrame()
+                }
+
+                measureRepeatedOnUiThread {
+                    performToggle(getTestCase())
+                    runWithTimingDisabled { doFramesUntilIdle() }
+                }
+            }
+        }
+    }
+
+    @Test
     fun layout() {
         benchmarkRule.benchmarkLayoutPerf(scrollerCaseFactory)
     }
@@ -93,3 +135,23 @@
         benchmarkRule.benchmarkDrawPerf(scrollerCaseFactory)
     }
 }
+
+// Below are forked from LazyBenchmarkCommon
+private fun ComposeExecutionControl.performToggle(testCase: MouseWheelScrollerTestCase) {
+    testCase.toggleState()
+    if (hasPendingChanges()) {
+        recompose()
+    }
+    if (hasPendingMeasureOrLayout()) {
+        getViewRoot().measureAndLayoutForTest()
+    }
+}
+
+private fun ComposeExecutionControl.assertNoPendingRecompositionMeasureOrLayout() {
+    if (hasPendingChanges() || hasPendingMeasureOrLayout()) {
+        throw AssertionError("Expected no pending changes but there were some.")
+    }
+}
+
+private fun ComposeExecutionControl.getViewRoot(): ViewRootForTest =
+    getHostView() as ViewRootForTest
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerTestCase.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerTestCase.kt
index 4c202c1..275e985 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerTestCase.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/ScrollerTestCase.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.foundation.benchmark
 
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.View
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.layout.Column
@@ -28,12 +31,15 @@
 import androidx.compose.testutils.ToggleableTestCase
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.runBlocking
 
 /**
  * Test case that puts a large number of boxes in a column in a vertical scroller to force
  * scrolling.
+ *
+ * [toggleState] calls [ScrollState.scrollTo] between oscillating values
  */
 class ScrollerTestCase : LayeredComposeTestCase(), ToggleableTestCase {
     private lateinit var scrollState: ScrollState
@@ -42,37 +48,117 @@
     override fun MeasuredContent() {
         scrollState = rememberScrollState()
         Column(Modifier.verticalScroll(scrollState)) {
-            Column(Modifier.fillMaxHeight()) {
-                for (green in 0..0xFF) {
-                    ColorStripe(0xFF, green, 0)
-                }
-                for (red in 0xFF downTo 0) {
-                    ColorStripe(red, 0xFF, 0)
-                }
-                for (blue in 0..0xFF) {
-                    ColorStripe(0, 0xFF, blue)
-                }
-                for (green in 0xFF downTo 0) {
-                    ColorStripe(0, green, 0xFF)
-                }
-                for (red in 0..0xFF) {
-                    ColorStripe(red, 0, 0xFF)
-                }
-                for (blue in 0xFF downTo 0) {
-                    ColorStripe(0xFF, 0, blue)
-                }
-            }
+            ColorStripes(step = 1, Modifier.fillMaxHeight())
         }
     }
 
     override fun toggleState() {
         runBlocking { scrollState.scrollTo(if (scrollState.value == 0) 10 else 0) }
     }
+}
+
+/**
+ * Test case that puts a large number of boxes in a column in a vertical scroller to force
+ * scrolling.
+ *
+ * [toggleState] injects mouse wheel events to scroll forward and backwards repeatedly.
+ */
+class MouseWheelScrollerTestCase() : LayeredComposeTestCase(), ToggleableTestCase {
+    private lateinit var scrollState: ScrollState
+    private var view: View? = null
+    private var currentEventTime: Long = 0
+    private var lastScrollUp: Boolean = true
 
     @Composable
-    fun ColorStripe(red: Int, green: Int, blue: Int) {
-        Canvas(Modifier.size(45.dp, 5.dp)) {
-            drawRect(Color(red = red, green = green, blue = blue))
+    override fun MeasuredContent() {
+        view = LocalView.current
+        scrollState = rememberScrollState()
+        Column(Modifier.verticalScroll(scrollState)) {
+            // A lower step causes benchmark issues due to the resulting size / number of nodes
+            ColorStripes(step = 5, Modifier.fillMaxHeight())
         }
     }
+
+    override fun toggleState() {
+        // For mouse wheel scroll, negative values scroll down
+        // Note: these aren't the actual values that will be used to scroll, depending on
+        // Android version these values will get converted into a different (larger) value.
+        // This also unfortunately includes an animation that cannot be disabled, so this
+        // is a best effort at some repeated scrolling
+        val scrollAmount =
+            if (lastScrollUp) {
+                lastScrollUp = false
+                -10
+            } else {
+                lastScrollUp = true
+                10
+            }
+        dispatchMouseWheelScroll(scrollAmount = scrollAmount, eventTime = currentEventTime, view!!)
+        currentEventTime += 10
+    }
+}
+
+@Composable
+private fun ColorStripes(step: Int, modifier: Modifier) {
+    Column(modifier) {
+        for (green in 0..0xFF step step) {
+            ColorStripe(0xFF, green, 0)
+        }
+        for (red in 0xFF downTo 0 step step) {
+            ColorStripe(red, 0xFF, 0)
+        }
+        for (blue in 0..0xFF step step) {
+            ColorStripe(0, 0xFF, blue)
+        }
+        for (green in 0xFF downTo 0 step step) {
+            ColorStripe(0, green, 0xFF)
+        }
+        for (red in 0..0xFF step step) {
+            ColorStripe(red, 0, 0xFF)
+        }
+        for (blue in 0xFF downTo 0 step step) {
+            ColorStripe(0xFF, 0, blue)
+        }
+    }
+}
+
+@Composable
+private fun ColorStripe(red: Int, green: Int, blue: Int) {
+    Canvas(Modifier.size(45.dp, 5.dp)) { drawRect(Color(red = red, green = green, blue = blue)) }
+}
+
+private fun dispatchMouseWheelScroll(
+    scrollAmount: Int,
+    eventTime: Long,
+    view: View,
+) {
+    val properties =
+        MotionEvent.PointerProperties().apply { toolType = MotionEvent.TOOL_TYPE_MOUSE }
+
+    val coords =
+        MotionEvent.PointerCoords().apply {
+            x = view.measuredWidth / 2f
+            y = view.measuredHeight / 2f
+            setAxisValue(MotionEvent.AXIS_VSCROLL, scrollAmount.toFloat())
+        }
+
+    val event =
+        MotionEvent.obtain(
+            0, /* downTime */
+            eventTime, /* eventTime */
+            MotionEvent.ACTION_SCROLL, /* action */
+            1, /* pointerCount */
+            arrayOf(properties),
+            arrayOf(coords),
+            0, /* metaState */
+            0, /* buttonState */
+            0f, /* xPrecision */
+            0f, /* yPrecision */
+            0, /* deviceId */
+            0, /* edgeFlags */
+            InputDevice.SOURCE_MOUSE, /* source */
+            0 /* flags */
+        )
+
+    view.dispatchGenericMotionEvent(event)
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/BasicTooltip.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/BasicTooltip.android.kt
index 1dc2f3d..95c91b5b 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/BasicTooltip.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/BasicTooltip.android.kt
@@ -16,31 +16,16 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.foundation.gestures.LongPressResult
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.waitForLongPress
-import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerEventType
-import androidx.compose.ui.input.pointer.PointerType
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.LiveRegionMode
-import androidx.compose.ui.semantics.liveRegion
-import androidx.compose.ui.semantics.onLongClick
-import androidx.compose.ui.semantics.paneTitle
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
-import androidx.compose.ui.window.PopupProperties
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
+
+internal actual object BasicTooltipStrings {
+    @Composable actual fun label(): String = stringResource(R.string.tooltip_label)
+
+    @Composable actual fun description(): String = stringResource(R.string.tooltip_description)
+}
 
 /**
  * BasicTooltipBox that wraps a composable with a tooltip.
@@ -62,157 +47,26 @@
  *   and mouse hover to trigger the tooltip through the state provided.
  * @param content the composable that the tooltip will anchor to.
  */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Maintained for binary compatibility.")
 @Composable
 @ExperimentalFoundationApi
-actual fun BasicTooltipBox(
+@JvmName("BasicTooltipBox")
+fun BasicTooltipBoxAndroid(
     positionProvider: PopupPositionProvider,
     tooltip: @Composable () -> Unit,
     state: BasicTooltipState,
-    modifier: Modifier,
-    focusable: Boolean,
-    enableUserInput: Boolean,
-    content: @Composable () -> Unit
-) {
-    val scope = rememberCoroutineScope()
-    Box {
-        if (state.isVisible) {
-            TooltipPopup(
-                positionProvider = positionProvider,
-                state = state,
-                scope = scope,
-                focusable = focusable,
-                content = tooltip
-            )
-        }
-
-        WrappedAnchor(
-            enableUserInput = enableUserInput,
-            state = state,
-            modifier = modifier,
-            content = content
-        )
-    }
-
-    DisposableEffect(state) { onDispose { state.onDispose() } }
-}
-
-@Composable
-@OptIn(ExperimentalFoundationApi::class)
-private fun WrappedAnchor(
-    enableUserInput: Boolean,
-    state: BasicTooltipState,
     modifier: Modifier = Modifier,
+    focusable: Boolean = true,
+    enableUserInput: Boolean = true,
     content: @Composable () -> Unit
 ) {
-    val scope = rememberCoroutineScope()
-    val longPressLabel = stringResource(R.string.tooltip_label)
-    Box(
-        modifier =
-            modifier
-                .handleGestures(enableUserInput, state)
-                .anchorSemantics(longPressLabel, enableUserInput, state, scope)
-    ) {
-        content()
-    }
+    BasicTooltipBox(
+        positionProvider = positionProvider,
+        tooltip = tooltip,
+        state = state,
+        modifier = modifier,
+        focusable = focusable,
+        enableUserInput = enableUserInput,
+        content = content,
+    )
 }
-
-@Composable
-@OptIn(ExperimentalFoundationApi::class)
-private fun TooltipPopup(
-    positionProvider: PopupPositionProvider,
-    state: BasicTooltipState,
-    scope: CoroutineScope,
-    focusable: Boolean,
-    content: @Composable () -> Unit
-) {
-    val tooltipDescription = stringResource(R.string.tooltip_description)
-    Popup(
-        popupPositionProvider = positionProvider,
-        onDismissRequest = {
-            if (state.isVisible) {
-                scope.launch { state.dismiss() }
-            }
-        },
-        properties = PopupProperties(focusable = focusable)
-    ) {
-        Box(
-            modifier =
-                Modifier.semantics {
-                    liveRegion = LiveRegionMode.Assertive
-                    paneTitle = tooltipDescription
-                }
-        ) {
-            content()
-        }
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-private fun Modifier.handleGestures(enabled: Boolean, state: BasicTooltipState): Modifier =
-    if (enabled) {
-        this.pointerInput(state) {
-                coroutineScope {
-                    awaitEachGesture {
-                        val pass = PointerEventPass.Initial
-
-                        // wait for the first down press
-                        val inputType = awaitFirstDown(pass = pass).type
-
-                        if (inputType == PointerType.Touch || inputType == PointerType.Stylus) {
-                            val longPress = waitForLongPress(pass = pass)
-                            if (longPress is LongPressResult.Success) {
-                                // handle long press - Show the tooltip
-                                launch { state.show(MutatePriority.UserInput) }
-
-                                // consume the children's click handling
-                                val changes = awaitPointerEvent(pass = pass).changes
-                                for (i in 0 until changes.size) {
-                                    changes[i].consume()
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            .pointerInput(state) {
-                coroutineScope {
-                    awaitPointerEventScope {
-                        val pass = PointerEventPass.Main
-
-                        while (true) {
-                            val event = awaitPointerEvent(pass)
-                            val inputType = event.changes[0].type
-                            if (inputType == PointerType.Mouse) {
-                                when (event.type) {
-                                    PointerEventType.Enter -> {
-                                        launch { state.show(MutatePriority.UserInput) }
-                                    }
-                                    PointerEventType.Exit -> {
-                                        state.dismiss()
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    } else this
-
-@OptIn(ExperimentalFoundationApi::class)
-private fun Modifier.anchorSemantics(
-    label: String,
-    enabled: Boolean,
-    state: BasicTooltipState,
-    scope: CoroutineScope
-): Modifier =
-    if (enabled) {
-        this.semantics(mergeDescendants = true) {
-            onLongClick(
-                label = label,
-                action = {
-                    scope.launch { state.show() }
-                    true
-                }
-            )
-        }
-    } else this
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.android.kt
index 295e75e..51b27f3 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.android.kt
@@ -22,7 +22,6 @@
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.requireView
-import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
index 63da5c3..911ae1f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
@@ -16,15 +16,36 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.gestures.LongPressResult
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForLongPress
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
+import androidx.compose.ui.window.PopupProperties
 import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
@@ -50,7 +71,7 @@
  */
 @Composable
 @ExperimentalFoundationApi
-expect fun BasicTooltipBox(
+fun BasicTooltipBox(
     positionProvider: PopupPositionProvider,
     tooltip: @Composable () -> Unit,
     state: BasicTooltipState,
@@ -58,7 +79,150 @@
     focusable: Boolean = true,
     enableUserInput: Boolean = true,
     content: @Composable () -> Unit
-)
+) {
+    val scope = rememberCoroutineScope()
+    Box {
+        if (state.isVisible) {
+            TooltipPopup(
+                positionProvider = positionProvider,
+                state = state,
+                scope = scope,
+                focusable = focusable,
+                content = tooltip
+            )
+        }
+
+        WrappedAnchor(
+            enableUserInput = enableUserInput,
+            state = state,
+            modifier = modifier,
+            content = content
+        )
+    }
+
+    DisposableEffect(state) { onDispose { state.onDispose() } }
+}
+
+@Composable
+@OptIn(ExperimentalFoundationApi::class)
+private fun WrappedAnchor(
+    enableUserInput: Boolean,
+    state: BasicTooltipState,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit
+) {
+    val scope = rememberCoroutineScope()
+    val longPressLabel = BasicTooltipStrings.label()
+    Box(
+        modifier =
+            modifier
+                .handleGestures(enableUserInput, state)
+                .anchorSemantics(longPressLabel, enableUserInput, state, scope)
+    ) {
+        content()
+    }
+}
+
+@Composable
+@OptIn(ExperimentalFoundationApi::class)
+private fun TooltipPopup(
+    positionProvider: PopupPositionProvider,
+    state: BasicTooltipState,
+    scope: CoroutineScope,
+    focusable: Boolean,
+    content: @Composable () -> Unit
+) {
+    val tooltipDescription = BasicTooltipStrings.description()
+    Popup(
+        popupPositionProvider = positionProvider,
+        onDismissRequest = {
+            if (state.isVisible) {
+                scope.launch { state.dismiss() }
+            }
+        },
+        properties = PopupProperties(focusable = focusable)
+    ) {
+        Box(
+            modifier =
+                Modifier.semantics {
+                    liveRegion = LiveRegionMode.Assertive
+                    paneTitle = tooltipDescription
+                }
+        ) {
+            content()
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun Modifier.handleGestures(enabled: Boolean, state: BasicTooltipState): Modifier =
+    if (enabled) {
+        this.pointerInput(state) {
+                coroutineScope {
+                    awaitEachGesture {
+                        val pass = PointerEventPass.Initial
+
+                        // wait for the first down press
+                        val inputType = awaitFirstDown(pass = pass).type
+
+                        if (inputType == PointerType.Touch || inputType == PointerType.Stylus) {
+                            val longPress = waitForLongPress(pass = pass)
+                            if (longPress is LongPressResult.Success) {
+                                // handle long press - Show the tooltip
+                                launch { state.show(MutatePriority.UserInput) }
+
+                                // consume the children's click handling
+                                val changes = awaitPointerEvent(pass = pass).changes
+                                for (i in 0 until changes.size) {
+                                    changes[i].consume()
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            .pointerInput(state) {
+                coroutineScope {
+                    awaitPointerEventScope {
+                        val pass = PointerEventPass.Main
+
+                        while (true) {
+                            val event = awaitPointerEvent(pass)
+                            val inputType = event.changes[0].type
+                            if (inputType == PointerType.Mouse) {
+                                when (event.type) {
+                                    PointerEventType.Enter -> {
+                                        launch { state.show(MutatePriority.UserInput) }
+                                    }
+                                    PointerEventType.Exit -> {
+                                        state.dismiss()
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+    } else this
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun Modifier.anchorSemantics(
+    label: String,
+    enabled: Boolean,
+    state: BasicTooltipState,
+    scope: CoroutineScope
+): Modifier =
+    if (enabled) {
+        this.semantics(mergeDescendants = true) {
+            onLongClick(
+                label = label,
+                action = {
+                    scope.launch { state.show() }
+                    true
+                }
+            )
+        }
+    } else this
 
 /**
  * Create and remember the default [BasicTooltipState].
@@ -216,3 +380,10 @@
      */
     const val TooltipDuration = 1500L
 }
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+internal expect object BasicTooltipStrings {
+    @Composable fun label(): String
+
+    @Composable fun description(): String
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt
index d9a21a4..94c22fb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt
@@ -26,24 +26,21 @@
 import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerEventType
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.util.VelocityTracker1D
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
-import kotlin.coroutines.coroutineContext
 import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.isActive
@@ -51,71 +48,27 @@
 import kotlinx.coroutines.supervisorScope
 import kotlinx.coroutines.withTimeoutOrNull
 
-internal class MouseWheelScrollNode(
+internal class MouseWheelScrollingLogic(
     private val scrollingLogic: ScrollingLogic,
+    private val mouseWheelScrollConfig: ScrollConfig,
     private val onScrollStopped: suspend (velocity: Velocity) -> Unit,
-    private var enabled: Boolean,
-) : DelegatingNode(), CompositionLocalConsumerModifierNode {
-
-    // Need to wait until onAttach to read the scroll config. Currently this is static, so we
-    // don't need to worry about observation / updating this over time.
-    private lateinit var mouseWheelScrollConfig: ScrollConfig
-
-    override fun onAttach() {
-        mouseWheelScrollConfig = platformScrollConfig()
-        coroutineScope.launch { receiveMouseWheelEvents() }
+    private var density: Density,
+) {
+    fun updateDensity(density: Density) {
+        this.density = density
     }
 
-    // Note that when `MouseWheelScrollNode` is used as a delegate of `ScrollableNode`,
-    // pointerInputNode does not get dispatched pointer events to in the standard manner because
-    // Modifier.Node.dispatchForKind does not dispatch to child/delegate nodes of the matching type,
-    // and `ScrollableNode` is already an instance of `PointerInputModifierNode`.
-    // This is worked around by having `MouseWheelScrollNode` simply forward the corresponding calls
-    // to pointerInputNode (hence its need to be `internal`).
-    internal val pointerInputNode =
-        delegate(
-            SuspendingPointerInputModifierNode {
-                if (enabled) {
-                    mouseWheelInput()
-                }
-            }
-        )
-
-    fun update(
-        enabled: Boolean,
-    ) {
-        var resetPointerInputHandling = false
-        if (this.enabled != enabled) {
-            this.enabled = enabled
-            resetPointerInputHandling = true
-        }
-        if (resetPointerInputHandling) {
-            pointerInputNode.resetPointerInputHandler()
-        }
-    }
-
-    private suspend fun PointerInputScope.mouseWheelInput() {
-        awaitPointerEventScope {
-            while (coroutineScope.isActive) {
-                val event = awaitScrollEvent()
-                if (!event.isConsumed) {
-                    val consumed = onMouseWheel(event)
-                    if (consumed) {
-                        event.consume()
-                    }
+    fun onPointerEvent(pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize) {
+        if (pass == PointerEventPass.Main && pointerEvent.type == PointerEventType.Scroll) {
+            if (!pointerEvent.isConsumed) {
+                val consumed = onMouseWheel(pointerEvent, bounds)
+                if (consumed) {
+                    pointerEvent.consume()
                 }
             }
         }
     }
 
-    private suspend fun AwaitPointerEventScope.awaitScrollEvent(): PointerEvent {
-        var event: PointerEvent
-        do {
-            event = awaitPointerEvent()
-        } while (event.type != PointerEventType.Scroll)
-        return event
-    }
-
     private inline val PointerEvent.isConsumed: Boolean
         get() = changes.fastAny { it.isConsumed }
 
@@ -143,13 +96,23 @@
     private val channel = Channel<MouseWheelScrollDelta>(capacity = Channel.UNLIMITED)
     private var isScrolling = false
 
-    private suspend fun receiveMouseWheelEvents() {
-        while (coroutineContext.isActive) {
-            val scrollDelta = channel.receive()
-            val density = currentValueOf(LocalDensity)
-            val threshold = with(density) { AnimationThreshold.toPx() }
-            val speed = with(density) { AnimationSpeed.toPx() }
-            scrollingLogic.dispatchMouseWheelScroll(scrollDelta, threshold, speed)
+    private var receivingMouseWheelEventsJob: Job? = null
+
+    fun startReceivingMouseWheelEvents(coroutineScope: CoroutineScope) {
+        if (receivingMouseWheelEventsJob == null) {
+            receivingMouseWheelEventsJob =
+                coroutineScope.launch {
+                    try {
+                        while (coroutineContext.isActive) {
+                            val scrollDelta = channel.receive()
+                            val threshold = with(density) { AnimationThreshold.toPx() }
+                            val speed = with(density) { AnimationSpeed.toPx() }
+                            scrollingLogic.dispatchMouseWheelScroll(scrollDelta, threshold, speed)
+                        }
+                    } finally {
+                        receivingMouseWheelEventsJob = null
+                    }
+                }
         }
     }
 
@@ -160,9 +123,11 @@
         isScrolling = false
     }
 
-    private fun PointerInputScope.onMouseWheel(pointerEvent: PointerEvent): Boolean {
+    private fun onMouseWheel(pointerEvent: PointerEvent, bounds: IntSize): Boolean {
         val scrollDelta =
-            with(mouseWheelScrollConfig) { calculateMouseWheelScroll(pointerEvent, size) }
+            with(mouseWheelScrollConfig) {
+                with(density) { calculateMouseWheelScroll(pointerEvent, bounds) }
+            }
         return if (scrollingLogic.canConsumeDelta(scrollDelta)) {
             channel
                 .trySend(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 93725b7..a7792c3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -275,7 +275,8 @@
         orientationLock = orientation
     ),
     KeyInputModifierNode,
-    SemanticsModifierNode {
+    SemanticsModifierNode,
+    CompositionLocalConsumerModifierNode {
 
     override val shouldAutoInvalidate: Boolean = false
 
@@ -309,7 +310,7 @@
     private var scrollByAction: ((x: Float, y: Float) -> Boolean)? = null
     private var scrollByOffsetAction: (suspend (Offset) -> Offset)? = null
 
-    private var mouseWheelScrollNode: MouseWheelScrollNode? = null
+    private var mouseWheelScrollingLogic: MouseWheelScrollingLogic? = null
 
     init {
         /** Nested scrolling */
@@ -352,15 +353,17 @@
     }
 
     private fun ensureMouseWheelScrollNodeInitialized() {
-        if (mouseWheelScrollNode != null) return
-        mouseWheelScrollNode =
-            delegate(
-                MouseWheelScrollNode(
+        if (mouseWheelScrollingLogic == null) {
+            mouseWheelScrollingLogic =
+                MouseWheelScrollingLogic(
                     scrollingLogic = scrollingLogic,
+                    mouseWheelScrollConfig = platformScrollConfig(),
                     onScrollStopped = ::onWheelScrollStopped,
-                    enabled = enabled,
+                    density = requireDensity()
                 )
-            )
+        }
+
+        mouseWheelScrollingLogic?.startReceivingMouseWheelEvents(coroutineScope)
     }
 
     fun update(
@@ -392,7 +395,6 @@
                 nestedScrollDispatcher = nestedScrollDispatcher
             )
         contentInViewNode.update(orientation, reverseDirection, bringIntoViewSpec)
-        mouseWheelScrollNode?.update(enabled = enabled)
 
         this.overscrollEffect = overscrollEffect
         this.flingBehavior = flingBehavior
@@ -414,6 +416,7 @@
 
     override fun onAttach() {
         updateDefaultFlingBehavior()
+        mouseWheelScrollingLogic?.updateDensity(requireDensity())
     }
 
     private fun updateDefaultFlingBehavior() {
@@ -425,7 +428,7 @@
     override fun onDensityChange() {
         onCancelPointerInput()
         updateDefaultFlingBehavior()
-        mouseWheelScrollNode?.pointerInputNode?.onDensityChange()
+        mouseWheelScrollingLogic?.updateDensity(requireDensity())
     }
 
     // Key handler for Page up/down scrolling behavior.
@@ -492,10 +495,12 @@
         if (pointerEvent.changes.fastAny { canDrag.invoke(it) }) {
             super.onPointerEvent(pointerEvent, pass, bounds)
         }
-        if (pass == PointerEventPass.Initial && pointerEvent.type == PointerEventType.Scroll) {
-            ensureMouseWheelScrollNodeInitialized()
+        if (enabled) {
+            if (pass == PointerEventPass.Initial && pointerEvent.type == PointerEventType.Scroll) {
+                ensureMouseWheelScrollNodeInitialized()
+            }
+            mouseWheelScrollingLogic?.onPointerEvent(pointerEvent, pass, bounds)
         }
-        mouseWheelScrollNode?.pointerInputNode?.onPointerEvent(pointerEvent, pass, bounds)
     }
 
     override fun SemanticsPropertyReceiver.applySemantics() {
@@ -521,16 +526,6 @@
         scrollByAction = null
         scrollByOffsetAction = null
     }
-
-    override fun onCancelPointerInput() {
-        super.onCancelPointerInput()
-        mouseWheelScrollNode?.pointerInputNode?.onCancelPointerInput()
-    }
-
-    override fun onViewConfigurationChange() {
-        super.onViewConfigurationChange()
-        mouseWheelScrollNode?.pointerInputNode?.onViewConfigurationChange()
-    }
 }
 
 /** Contains the default values used by [scrollable] */
diff --git a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/BasicTooltip.commonStubs.kt b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/BasicTooltip.commonStubs.kt
index 22ff0c8..892f259 100644
--- a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/BasicTooltip.commonStubs.kt
+++ b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/BasicTooltip.commonStubs.kt
@@ -17,17 +17,10 @@
 package androidx.compose.foundation
 
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.window.PopupPositionProvider
 
-@Composable
-@ExperimentalFoundationApi
-actual fun BasicTooltipBox(
-    positionProvider: PopupPositionProvider,
-    tooltip: @Composable () -> Unit,
-    state: BasicTooltipState,
-    modifier: Modifier,
-    focusable: Boolean,
-    enableUserInput: Boolean,
-    content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+internal actual object BasicTooltipStrings {
+    @Composable actual fun label(): String = implementedInJetBrainsFork()
+
+    @Composable actual fun description(): String = implementedInJetBrainsFork()
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2f679dbf..98fd41b 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -2868,6 +2868,8 @@
   }
 
   public final class TooltipKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDismissRequest, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2892,9 +2894,9 @@
 
   public final class Tooltip_androidKt {
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2f679dbf..98fd41b 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -2868,6 +2868,8 @@
   }
 
   public final class TooltipKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDismissRequest, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2892,9 +2894,9 @@
 
   public final class Tooltip_androidKt {
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional float maxWidth, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt
deleted file mode 100644
index d214bbd..0000000
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material3
-
-import androidx.activity.BackEventCompat
-import androidx.activity.compose.PredictiveBackHandler
-import androidx.compose.animation.core.animate
-import androidx.compose.material3.internal.PredictiveBack
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.launch
-
-/**
- * Registers a [PredictiveBackHandler] and provides animation values in [DrawerPredictiveBackState]
- * based on back progress.
- *
- * @param drawerState state of the drawer
- * @param content content of the rest of the UI
- */
-@Composable
-internal actual fun DrawerPredictiveBackHandler(
-    drawerState: DrawerState,
-    content: @Composable (DrawerPredictiveBackState) -> Unit
-) {
-    val drawerPredictiveBackState = remember { DrawerPredictiveBackState() }
-    val scope = rememberCoroutineScope()
-    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val maxScaleXDistanceGrow: Float
-    val maxScaleXDistanceShrink: Float
-    val maxScaleYDistance: Float
-    with(LocalDensity.current) {
-        maxScaleXDistanceGrow = PredictiveBackDrawerMaxScaleXDistanceGrow.toPx()
-        maxScaleXDistanceShrink = PredictiveBackDrawerMaxScaleXDistanceShrink.toPx()
-        maxScaleYDistance = PredictiveBackDrawerMaxScaleYDistance.toPx()
-    }
-
-    PredictiveBackHandler(enabled = drawerState.isOpen) { progress ->
-        try {
-            progress.collect { backEvent ->
-                drawerPredictiveBackState.update(
-                    PredictiveBack.transform(backEvent.progress),
-                    backEvent.swipeEdge == BackEventCompat.EDGE_LEFT,
-                    isRtl,
-                    maxScaleXDistanceGrow,
-                    maxScaleXDistanceShrink,
-                    maxScaleYDistance
-                )
-            }
-        } catch (e: CancellationException) {
-            drawerPredictiveBackState.clear()
-        } finally {
-            if (drawerPredictiveBackState.swipeEdgeMatchesDrawer) {
-                // If swipe edge matches drawer gravity and we've stretched the drawer horizontally,
-                // un-stretch it smoothly so that it hides completely during the drawer close.
-                scope.launch {
-                    animate(
-                        initialValue = drawerPredictiveBackState.scaleXDistance,
-                        targetValue = 0f
-                    ) { value, _ ->
-                        drawerPredictiveBackState.scaleXDistance = value
-                    }
-                    drawerPredictiveBackState.clear()
-                }
-            }
-            drawerState.close()
-        }
-    }
-
-    LaunchedEffect(drawerState.isClosed) {
-        if (drawerState.isClosed) {
-            drawerPredictiveBackState.clear()
-        }
-    }
-
-    content(drawerPredictiveBackState)
-}
-
-internal val PredictiveBackDrawerMaxScaleXDistanceGrow = 12.dp
-internal val PredictiveBackDrawerMaxScaleXDistanceShrink = 24.dp
-internal val PredictiveBackDrawerMaxScaleYDistance = 48.dp
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
index ffa265c..d1c12d9 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
@@ -16,33 +16,22 @@
 
 package androidx.compose.material3
 
-import android.content.res.Configuration
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.foundation.layout.requiredHeightIn
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.ElevationTokens
 import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.CacheDrawScope
-import androidx.compose.ui.draw.DrawResult
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.isSpecified
+
+@Composable
+internal actual fun windowContainerWidthInPx(): Int {
+    return with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.roundToPx() }
+}
 
 /**
  * Plain tooltip that provides a descriptive message.
@@ -60,21 +49,21 @@
  * @param shadowElevation the shadow elevation of the tooltip.
  * @param content the composable that will be used to populate the tooltip's content.
  */
-@Suppress("DEPRECATION")
 @Deprecated(
     level = DeprecationLevel.HIDDEN,
     message = "Maintained for binary compatibility. " + "Use overload with maxWidth parameter."
 )
 @Composable
 @ExperimentalMaterial3Api
-actual fun TooltipScope.PlainTooltip(
-    modifier: Modifier,
-    caretSize: DpSize,
-    shape: Shape,
-    contentColor: Color,
-    containerColor: Color,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
+@JvmName("PlainTooltip")
+fun TooltipScope.PlainTooltipAndroid(
+    modifier: Modifier = Modifier,
+    caretSize: DpSize = DpSize.Unspecified,
+    shape: Shape = TooltipDefaults.plainTooltipContainerShape,
+    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
+    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
+    tonalElevation: Dp = 0.dp,
+    shadowElevation: Dp = 0.dp,
     content: @Composable () -> Unit
 ) =
     PlainTooltip(
@@ -106,59 +95,32 @@
  * @param shadowElevation the shadow elevation of the tooltip.
  * @param content the composable that will be used to populate the tooltip's content.
  */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Maintained for binary compatibility.")
 @Composable
 @ExperimentalMaterial3Api
-actual fun TooltipScope.PlainTooltip(
-    modifier: Modifier,
-    caretSize: DpSize,
-    maxWidth: Dp,
-    shape: Shape,
-    contentColor: Color,
-    containerColor: Color,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
+@JvmName("PlainTooltip")
+fun TooltipScope.PlainTooltipAndroid(
+    modifier: Modifier = Modifier,
+    caretSize: DpSize = DpSize.Unspecified,
+    maxWidth: Dp = TooltipDefaults.plainTooltipMaxWidth,
+    shape: Shape = TooltipDefaults.plainTooltipContainerShape,
+    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
+    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
+    tonalElevation: Dp = 0.dp,
+    shadowElevation: Dp = 0.dp,
     content: @Composable () -> Unit
 ) {
-    val drawCaretModifier =
-        if (caretSize.isSpecified) {
-            val density = LocalDensity.current
-            val configuration = LocalConfiguration.current
-            Modifier.drawCaret { anchorLayoutCoordinates ->
-                    drawCaretWithPath(
-                        density,
-                        configuration,
-                        containerColor,
-                        caretSize,
-                        anchorLayoutCoordinates
-                    )
-                }
-                .then(modifier)
-        } else modifier
-    Surface(
-        modifier = drawCaretModifier,
+    PlainTooltip(
+        modifier = modifier,
+        caretSize = caretSize,
+        maxWidth = maxWidth,
         shape = shape,
-        color = containerColor,
+        contentColor = contentColor,
+        containerColor = containerColor,
         tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation
-    ) {
-        Box(
-            modifier =
-                Modifier.sizeIn(
-                        minWidth = TooltipMinWidth,
-                        maxWidth = maxWidth,
-                        minHeight = TooltipMinHeight
-                    )
-                    .padding(PlainTooltipContentPadding)
-        ) {
-            val textStyle = PlainTooltipTokens.SupportingTextFont.value
-
-            CompositionLocalProvider(
-                LocalContentColor provides contentColor,
-                LocalTextStyle provides textStyle,
-                content = content
-            )
-        }
-    }
+        shadowElevation = shadowElevation,
+        content = content,
+    )
 }
 
 /**
@@ -179,22 +141,22 @@
  * @param shadowElevation the shadow elevation of the tooltip.
  * @param text the composable that will be used to populate the rich tooltip's text.
  */
-@Suppress("DEPRECATION")
 @Deprecated(
     level = DeprecationLevel.HIDDEN,
     message = "Maintained for binary compatibility. " + "Use overload with maxWidth parameter."
 )
 @Composable
 @ExperimentalMaterial3Api
-actual fun TooltipScope.RichTooltip(
-    modifier: Modifier,
-    title: (@Composable () -> Unit)?,
-    action: (@Composable () -> Unit)?,
-    caretSize: DpSize,
-    shape: Shape,
-    colors: RichTooltipColors,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
+@JvmName("RichTooltip")
+fun TooltipScope.RichTooltipAndroid(
+    modifier: Modifier = Modifier,
+    title: (@Composable () -> Unit)? = null,
+    action: (@Composable () -> Unit)? = null,
+    caretSize: DpSize = DpSize.Unspecified,
+    shape: Shape = TooltipDefaults.richTooltipContainerShape,
+    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
+    tonalElevation: Dp = ElevationTokens.Level0,
+    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
     text: @Composable () -> Unit
 ) =
     RichTooltip(
@@ -229,171 +191,32 @@
  * @param shadowElevation the shadow elevation of the tooltip.
  * @param text the composable that will be used to populate the rich tooltip's text.
  */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "Maintained for binary compatibility.")
 @Composable
 @ExperimentalMaterial3Api
-actual fun TooltipScope.RichTooltip(
-    modifier: Modifier,
-    title: (@Composable () -> Unit)?,
-    action: (@Composable () -> Unit)?,
-    caretSize: DpSize,
-    maxWidth: Dp,
-    shape: Shape,
-    colors: RichTooltipColors,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
+@JvmName("RichTooltip")
+fun TooltipScope.RichTooltipAndroid(
+    modifier: Modifier = Modifier,
+    title: (@Composable () -> Unit)? = null,
+    action: (@Composable () -> Unit)? = null,
+    caretSize: DpSize = DpSize.Unspecified,
+    maxWidth: Dp = TooltipDefaults.richTooltipMaxWidth,
+    shape: Shape = TooltipDefaults.richTooltipContainerShape,
+    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
+    tonalElevation: Dp = ElevationTokens.Level0,
+    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
     text: @Composable () -> Unit
 ) {
-    val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
-    val elevatedColor =
-        MaterialTheme.colorScheme.applyTonalElevation(colors.containerColor, absoluteElevation)
-    val drawCaretModifier =
-        if (caretSize.isSpecified) {
-            val density = LocalDensity.current
-            val configuration = LocalConfiguration.current
-            Modifier.drawCaret { anchorLayoutCoordinates ->
-                    drawCaretWithPath(
-                        density,
-                        configuration,
-                        elevatedColor,
-                        caretSize,
-                        anchorLayoutCoordinates
-                    )
-                }
-                .then(modifier)
-        } else modifier
-    Surface(
-        modifier =
-            drawCaretModifier.sizeIn(
-                minWidth = TooltipMinWidth,
-                maxWidth = maxWidth,
-                minHeight = TooltipMinHeight
-            ),
+    RichTooltip(
+        modifier = modifier,
+        title = title,
+        action = action,
+        caretSize = caretSize,
+        maxWidth = maxWidth,
         shape = shape,
-        color = colors.containerColor,
+        colors = colors,
         tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation
-    ) {
-        val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
-        val subheadTextStyle = RichTooltipTokens.SubheadFont.value
-        val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
-
-        Column(modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)) {
-            title?.let {
-                Box(modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.titleContentColor,
-                        LocalTextStyle provides subheadTextStyle,
-                        content = it
-                    )
-                }
-            }
-            Box(modifier = Modifier.textVerticalPadding(title != null, action != null)) {
-                CompositionLocalProvider(
-                    LocalContentColor provides colors.contentColor,
-                    LocalTextStyle provides supportingTextStyle,
-                    content = text
-                )
-            }
-            action?.let {
-                Box(
-                    modifier =
-                        Modifier.requiredHeightIn(min = ActionLabelMinHeight)
-                            .padding(bottom = ActionLabelBottomPadding)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.actionContentColor,
-                        LocalTextStyle provides actionLabelTextStyle,
-                        content = it
-                    )
-                }
-            }
-        }
-    }
-}
-
-@ExperimentalMaterial3Api
-private fun CacheDrawScope.drawCaretWithPath(
-    density: Density,
-    configuration: Configuration,
-    containerColor: Color,
-    caretSize: DpSize,
-    anchorLayoutCoordinates: LayoutCoordinates?
-): DrawResult {
-    val path = Path()
-
-    if (anchorLayoutCoordinates != null) {
-        val caretHeightPx: Int
-        val caretWidthPx: Int
-        val screenWidthPx: Int
-        val tooltipAnchorSpacing: Int
-        with(density) {
-            caretHeightPx = caretSize.height.roundToPx()
-            caretWidthPx = caretSize.width.roundToPx()
-            screenWidthPx = configuration.screenWidthDp.dp.roundToPx()
-            tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
-        }
-        val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
-        val anchorLeft = anchorBounds.left
-        val anchorRight = anchorBounds.right
-        val anchorTop = anchorBounds.top
-        val anchorMid = (anchorRight + anchorLeft) / 2
-        val anchorWidth = anchorRight - anchorLeft
-        val tooltipWidth = this.size.width
-        val tooltipHeight = this.size.height
-        val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
-        val caretY =
-            if (isCaretTop) {
-                0f
-            } else {
-                tooltipHeight
-            }
-
-        // Default the caret to be in the middle
-        // caret might need to be offset depending on where
-        // the tooltip is placed relative to the anchor
-        var position: Offset =
-            if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
-                Offset(anchorMid, caretY)
-            } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
-                val anchorMidFromRightScreenEdge = screenWidthPx - anchorMid
-                val caretX = tooltipWidth - anchorMidFromRightScreenEdge
-                Offset(caretX, caretY)
-            } else {
-                Offset(tooltipWidth / 2, caretY)
-            }
-        if (anchorMid - tooltipWidth / 2 < 0) {
-            // The tooltip needs to be start aligned if it would collide with the left side of
-            // screen.
-            position = Offset(anchorMid - anchorLeft, caretY)
-        } else if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
-            // The tooltip needs to be end aligned if it would collide with the right side of the
-            // screen.
-            position = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
-        }
-
-        if (isCaretTop) {
-            path.apply {
-                moveTo(x = position.x, y = position.y)
-                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
-                lineTo(x = position.x, y = position.y - caretHeightPx)
-                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
-                close()
-            }
-        } else {
-            path.apply {
-                moveTo(x = position.x, y = position.y)
-                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
-                lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
-                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
-                close()
-            }
-        }
-    }
-
-    return onDrawWithContent {
-        if (anchorLayoutCoordinates != null) {
-            drawContent()
-            drawPath(path = path, color = containerColor)
-        }
-    }
+        shadowElevation = shadowElevation,
+        text = text,
+    )
 }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
index e7d8212..db71747 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/BasicTooltip.android.kt
@@ -14,241 +14,14 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalMaterial3Api::class)
-
 package androidx.compose.material3.internal
 
-import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.R
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.waitForUpOrCancellation
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.TooltipState
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
-import androidx.compose.ui.input.pointer.PointerEventType
-import androidx.compose.ui.input.pointer.PointerType
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.LiveRegionMode
-import androidx.compose.ui.semantics.liveRegion
-import androidx.compose.ui.semantics.onLongClick
-import androidx.compose.ui.semantics.paneTitle
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-import androidx.compose.ui.window.PopupProperties
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
 
-/**
- * NOTICE: Fork from androidx.compose.foundation.BasicTooltip box since those are experimental
- *
- * BasicTooltipBox that wraps a composable with a tooltip.
- *
- * Tooltip that provides a descriptive message for an anchor. It can be used to call the users
- * attention to the anchor.
- *
- * @param positionProvider [PopupPositionProvider] that will be used to place the tooltip relative
- *   to the anchor content.
- * @param tooltip the composable that will be used to populate the tooltip's content.
- * @param state handles the state of the tooltip's visibility.
- * @param modifier the [Modifier] to be applied to this BasicTooltipBox.
- * @param focusable [Boolean] that determines if the tooltip is focusable. When true, the tooltip
- *   will consume touch events while it's shown and will have accessibility focus move to the first
- *   element of the component. When false, the tooltip won't consume touch events while it's shown
- *   but assistive-tech users will need to swipe or drag to get to the first element of the
- *   component.
- * @param enableUserInput [Boolean] which determines if this BasicTooltipBox will handle long press
- *   and mouse hover to trigger the tooltip through the state provided.
- * @param content the composable that the tooltip will anchor to.
- */
-@Composable
-@ExperimentalMaterial3Api
-internal actual fun BasicTooltipBox(
-    positionProvider: PopupPositionProvider,
-    tooltip: @Composable () -> Unit,
-    state: TooltipState,
-    modifier: Modifier,
-    onDismissRequest: (() -> Unit)?,
-    focusable: Boolean,
-    enableUserInput: Boolean,
-    content: @Composable () -> Unit
-) {
-    val scope = rememberCoroutineScope()
-    Box {
-        if (state.isVisible) {
-            TooltipPopup(
-                positionProvider = positionProvider,
-                state = state,
-                onDismissRequest = onDismissRequest,
-                scope = scope,
-                focusable = focusable,
-                content = tooltip
-            )
-        }
+internal actual object BasicTooltipStrings {
+    @Composable actual fun label(): String = stringResource(R.string.tooltip_label)
 
-        WrappedAnchor(
-            enableUserInput = enableUserInput,
-            state = state,
-            modifier = modifier,
-            content = content
-        )
-    }
-
-    DisposableEffect(state) { onDispose { state.onDispose() } }
+    @Composable actual fun description(): String = stringResource(R.string.tooltip_description)
 }
-
-@Composable
-private fun WrappedAnchor(
-    enableUserInput: Boolean,
-    state: TooltipState,
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit
-) {
-    val scope = rememberCoroutineScope()
-    val longPressLabel = stringResource(R.string.tooltip_label)
-    Box(
-        modifier =
-            modifier
-                .handleGestures(enableUserInput, state)
-                .anchorSemantics(longPressLabel, enableUserInput, state, scope)
-    ) {
-        content()
-    }
-}
-
-@Composable
-private fun TooltipPopup(
-    positionProvider: PopupPositionProvider,
-    state: TooltipState,
-    onDismissRequest: (() -> Unit)?,
-    scope: CoroutineScope,
-    focusable: Boolean,
-    content: @Composable () -> Unit
-) {
-    val tooltipDescription = stringResource(R.string.tooltip_description)
-    Popup(
-        popupPositionProvider = positionProvider,
-        onDismissRequest = {
-            if (onDismissRequest == null) {
-                if (state.isVisible) {
-                    scope.launch { state.dismiss() }
-                }
-            } else {
-                onDismissRequest()
-            }
-        },
-        properties = PopupProperties(focusable = focusable)
-    ) {
-        Box(
-            modifier =
-                Modifier.semantics {
-                    liveRegion = LiveRegionMode.Assertive
-                    paneTitle = tooltipDescription
-                }
-        ) {
-            content()
-        }
-    }
-}
-
-private fun Modifier.handleGestures(enabled: Boolean, state: TooltipState): Modifier =
-    if (enabled) {
-        this.pointerInput(state) {
-                coroutineScope {
-                    awaitEachGesture {
-                        // Long press will finish before or after show so keep track of it, in a
-                        // flow to handle both cases
-                        val isLongPressedFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
-                        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
-                        val pass = PointerEventPass.Initial
-                        // wait for the first down press
-                        val inputType = awaitFirstDown(pass = pass).type
-
-                        if (inputType == PointerType.Touch || inputType == PointerType.Stylus) {
-                            try {
-                                // listen to if there is up gesture
-                                // within the longPressTimeout limit
-                                withTimeout(longPressTimeout) {
-                                    waitForUpOrCancellation(pass = pass)
-                                }
-                            } catch (_: PointerEventTimeoutCancellationException) {
-                                // handle long press - Show the tooltip
-                                launch(start = CoroutineStart.UNDISPATCHED) {
-                                    try {
-                                        isLongPressedFlow.tryEmit(true)
-                                        state.show(MutatePriority.PreventUserInput)
-                                    } finally {
-                                        if (state.isVisible) {
-                                            isLongPressedFlow.collectLatest { isLongPressed ->
-                                                if (!isLongPressed) {
-                                                    state.dismiss()
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-
-                                // consume the children's click handling
-                                // Long press may still be in progress
-                                val upEvent = waitForUpOrCancellation(pass = pass)
-                                upEvent?.consume()
-                            } finally {
-                                isLongPressedFlow.tryEmit(false)
-                            }
-                        }
-                    }
-                }
-            }
-            .pointerInput(state) {
-                coroutineScope {
-                    awaitPointerEventScope {
-                        val pass = PointerEventPass.Main
-
-                        while (true) {
-                            val event = awaitPointerEvent(pass)
-                            val inputType = event.changes[0].type
-                            if (inputType == PointerType.Mouse) {
-                                when (event.type) {
-                                    PointerEventType.Enter -> {
-                                        launch { state.show(MutatePriority.UserInput) }
-                                    }
-                                    PointerEventType.Exit -> {
-                                        state.dismiss()
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    } else this
-
-private fun Modifier.anchorSemantics(
-    label: String,
-    enabled: Boolean,
-    state: TooltipState,
-    scope: CoroutineScope
-): Modifier =
-    if (enabled) {
-        this.parentSemantics {
-            onLongClick(
-                label = label,
-                action = {
-                    scope.launch { state.show() }
-                    true
-                }
-            )
-        }
-    } else this
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index 09ba3d4..391938b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -45,8 +45,11 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.material3.internal.AnchoredDraggableState
+import androidx.compose.material3.internal.BackEventCompat
 import androidx.compose.material3.internal.DraggableAnchors
 import androidx.compose.material3.internal.FloatProducer
+import androidx.compose.material3.internal.PredictiveBack
+import androidx.compose.material3.internal.PredictiveBackHandler
 import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.anchoredDraggable
 import androidx.compose.material3.internal.getString
@@ -58,6 +61,7 @@
 import androidx.compose.material3.tokens.ScrimTokens
 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.State
@@ -934,11 +938,70 @@
     }
 }
 
+/**
+ * Registers a [PredictiveBackHandler] and provides animation values in [DrawerPredictiveBackState]
+ * based on back progress.
+ *
+ * @param drawerState state of the drawer
+ * @param content content of the rest of the UI
+ */
 @Composable
-internal expect fun DrawerPredictiveBackHandler(
+internal fun DrawerPredictiveBackHandler(
     drawerState: DrawerState,
     content: @Composable (DrawerPredictiveBackState) -> Unit
-)
+) {
+    val drawerPredictiveBackState = remember { DrawerPredictiveBackState() }
+    val scope = rememberCoroutineScope()
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val maxScaleXDistanceGrow: Float
+    val maxScaleXDistanceShrink: Float
+    val maxScaleYDistance: Float
+    with(LocalDensity.current) {
+        maxScaleXDistanceGrow = PredictiveBackDrawerMaxScaleXDistanceGrow.toPx()
+        maxScaleXDistanceShrink = PredictiveBackDrawerMaxScaleXDistanceShrink.toPx()
+        maxScaleYDistance = PredictiveBackDrawerMaxScaleYDistance.toPx()
+    }
+
+    PredictiveBackHandler(enabled = drawerState.isOpen) { progress ->
+        try {
+            progress.collect { backEvent ->
+                drawerPredictiveBackState.update(
+                    PredictiveBack.transform(backEvent.progress),
+                    backEvent.swipeEdge == BackEventCompat.EDGE_LEFT,
+                    isRtl,
+                    maxScaleXDistanceGrow,
+                    maxScaleXDistanceShrink,
+                    maxScaleYDistance
+                )
+            }
+        } catch (e: kotlin.coroutines.cancellation.CancellationException) {
+            drawerPredictiveBackState.clear()
+        } finally {
+            if (drawerPredictiveBackState.swipeEdgeMatchesDrawer) {
+                // If swipe edge matches drawer gravity and we've stretched the drawer horizontally,
+                // un-stretch it smoothly so that it hides completely during the drawer close.
+                scope.launch {
+                    animate(
+                        initialValue = drawerPredictiveBackState.scaleXDistance,
+                        targetValue = 0f
+                    ) { value, _ ->
+                        drawerPredictiveBackState.scaleXDistance = value
+                    }
+                    drawerPredictiveBackState.clear()
+                }
+            }
+            drawerState.close()
+        }
+    }
+
+    LaunchedEffect(drawerState.isClosed) {
+        if (drawerState.isClosed) {
+            drawerPredictiveBackState.clear()
+        }
+    }
+
+    content(drawerPredictiveBackState)
+}
 
 /** Object to hold default values for [ModalNavigationDrawer] */
 object DrawerDefaults {
@@ -1252,6 +1315,10 @@
 private val DrawerVelocityThreshold = 400.dp
 private val MinimumDrawerWidth = 240.dp
 
+internal val PredictiveBackDrawerMaxScaleXDistanceGrow = 12.dp
+internal val PredictiveBackDrawerMaxScaleXDistanceShrink = 24.dp
+internal val PredictiveBackDrawerMaxScaleYDistance = 48.dp
+
 // TODO: b/177571613 this should be a proper decay settling
 // this is taken from the DrawerLayout's DragViewHelper as a min duration.
 private val AnchoredDraggableDefaultAnimationSpec = TweenSpec<Float>(durationMillis = 256)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 50dffb1..11e2e8f 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -23,9 +23,12 @@
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.internal.BasicTooltipBox
 import androidx.compose.material3.internal.BasicTooltipDefaults
 import androidx.compose.material3.tokens.ElevationTokens
@@ -33,6 +36,7 @@
 import androidx.compose.material3.tokens.PlainTooltipTokens
 import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.Stable
@@ -44,14 +48,18 @@
 import androidx.compose.ui.draw.CacheDrawScope
 import androidx.compose.ui.draw.DrawResult
 import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.takeOrElse
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntOffset
@@ -59,6 +67,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
 import androidx.compose.ui.window.PopupPositionProvider
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -258,40 +267,6 @@
  * @param caretSize [DpSize] for the caret of the tooltip, if a default caret is desired with a
  *   specific dimension. Please see [TooltipDefaults.caretSize] to see the default dimensions. Pass
  *   in Dp.Unspecified for this parameter if no caret is desired.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param contentColor [Color] that will be applied to the tooltip's content.
- * @param containerColor [Color] that will be applied to the tooltip's container.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param content the composable that will be used to populate the tooltip's content.
- */
-@Suppress("DEPRECATION")
-@Deprecated(
-    level = DeprecationLevel.HIDDEN,
-    message = "Maintained for binary compatibility. " + "Use overload with maxWidth parameter."
-)
-@Composable
-@ExperimentalMaterial3Api
-expect fun TooltipScope.PlainTooltip(
-    modifier: Modifier = Modifier,
-    caretSize: DpSize = DpSize.Unspecified,
-    shape: Shape = TooltipDefaults.plainTooltipContainerShape,
-    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
-    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
-    tonalElevation: Dp = 0.dp,
-    shadowElevation: Dp = 0.dp,
-    content: @Composable () -> Unit
-)
-
-/**
- * Plain tooltip that provides a descriptive message.
- *
- * Usually used with [TooltipBox].
- *
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param caretSize [DpSize] for the caret of the tooltip, if a default caret is desired with a
- *   specific dimension. Please see [TooltipDefaults.caretSize] to see the default dimensions. Pass
- *   in Dp.Unspecified for this parameter if no caret is desired.
  * @param maxWidth the maximum width for the plain tooltip
  * @param shape the [Shape] that should be applied to the tooltip container.
  * @param contentColor [Color] that will be applied to the tooltip's content.
@@ -302,7 +277,7 @@
  */
 @Composable
 @ExperimentalMaterial3Api
-expect fun TooltipScope.PlainTooltip(
+fun TooltipScope.PlainTooltip(
     modifier: Modifier = Modifier,
     caretSize: DpSize = DpSize.Unspecified,
     maxWidth: Dp = TooltipDefaults.plainTooltipMaxWidth,
@@ -312,44 +287,48 @@
     tonalElevation: Dp = 0.dp,
     shadowElevation: Dp = 0.dp,
     content: @Composable () -> Unit
-)
+) {
+    val drawCaretModifier =
+        if (caretSize.isSpecified) {
+            val density = LocalDensity.current
+            val windowContainerWidthInPx = windowContainerWidthInPx()
+            Modifier.drawCaret { anchorLayoutCoordinates ->
+                    drawCaretWithPath(
+                        density,
+                        windowContainerWidthInPx,
+                        containerColor,
+                        caretSize,
+                        anchorLayoutCoordinates
+                    )
+                }
+                .then(modifier)
+        } else modifier
+    Surface(
+        modifier = drawCaretModifier,
+        shape = shape,
+        color = containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        Box(
+            modifier =
+                Modifier.sizeIn(
+                        minWidth = TooltipMinWidth,
+                        maxWidth = maxWidth,
+                        minHeight = TooltipMinHeight
+                    )
+                    .padding(PlainTooltipContentPadding)
+        ) {
+            val textStyle = PlainTooltipTokens.SupportingTextFont.value
 
-/**
- * Rich text tooltip that allows the user to pass in a title, text, and action. Tooltips are used to
- * provide a descriptive message.
- *
- * Usually used with [TooltipBox]
- *
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param title An optional title for the tooltip.
- * @param action An optional action for the tooltip.
- * @param caretSize [DpSize] for the caret of the tooltip, if a default caret is desired with a
- *   specific dimension. Please see [TooltipDefaults.caretSize] to see the default dimensions. Pass
- *   in Dp.Unspecified for this parameter if no caret is desired.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param text the composable that will be used to populate the rich tooltip's text.
- */
-@Suppress("DEPRECATION")
-@Deprecated(
-    level = DeprecationLevel.HIDDEN,
-    message = "Maintained for binary compatibility. " + "Use overload with maxWidth parameter."
-)
-@Composable
-@ExperimentalMaterial3Api
-expect fun TooltipScope.RichTooltip(
-    modifier: Modifier = Modifier,
-    title: (@Composable () -> Unit)? = null,
-    action: (@Composable () -> Unit)? = null,
-    caretSize: DpSize = DpSize.Unspecified,
-    shape: Shape = TooltipDefaults.richTooltipContainerShape,
-    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
-    tonalElevation: Dp = ElevationTokens.Level0,
-    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
-    text: @Composable () -> Unit
-)
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor,
+                LocalTextStyle provides textStyle,
+                content = content
+            )
+        }
+    }
+}
 
 /**
  * Rich text tooltip that allows the user to pass in a title, text, and action. Tooltips are used to
@@ -372,7 +351,7 @@
  */
 @Composable
 @ExperimentalMaterial3Api
-expect fun TooltipScope.RichTooltip(
+fun TooltipScope.RichTooltip(
     modifier: Modifier = Modifier,
     title: (@Composable () -> Unit)? = null,
     action: (@Composable () -> Unit)? = null,
@@ -383,7 +362,74 @@
     tonalElevation: Dp = ElevationTokens.Level0,
     shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
     text: @Composable () -> Unit
-)
+) {
+    val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
+    val elevatedColor =
+        MaterialTheme.colorScheme.applyTonalElevation(colors.containerColor, absoluteElevation)
+    val drawCaretModifier =
+        if (caretSize.isSpecified) {
+            val density = LocalDensity.current
+            val windowContainerWidthInPx = windowContainerWidthInPx()
+            Modifier.drawCaret { anchorLayoutCoordinates ->
+                    drawCaretWithPath(
+                        density,
+                        windowContainerWidthInPx,
+                        elevatedColor,
+                        caretSize,
+                        anchorLayoutCoordinates
+                    )
+                }
+                .then(modifier)
+        } else modifier
+    Surface(
+        modifier =
+            drawCaretModifier.sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = maxWidth,
+                minHeight = TooltipMinHeight
+            ),
+        shape = shape,
+        color = colors.containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
+        val subheadTextStyle = RichTooltipTokens.SubheadFont.value
+        val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
+
+        Column(modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)) {
+            title?.let {
+                Box(modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.titleContentColor,
+                        LocalTextStyle provides subheadTextStyle,
+                        content = it
+                    )
+                }
+            }
+            Box(modifier = Modifier.textVerticalPadding(title != null, action != null)) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.contentColor,
+                    LocalTextStyle provides supportingTextStyle,
+                    content = text
+                )
+            }
+            action?.let {
+                Box(
+                    modifier =
+                        Modifier.requiredHeightIn(min = ActionLabelMinHeight)
+                            .padding(bottom = ActionLabelBottomPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.actionContentColor,
+                        LocalTextStyle provides actionLabelTextStyle,
+                        content = it
+                    )
+                }
+            }
+        }
+    }
+}
 
 /** Tooltip defaults that contain default values for both [PlainTooltip] and [RichTooltip] */
 @ExperimentalMaterial3Api
@@ -821,6 +867,95 @@
         this.graphicsLayer(scaleX = scale, scaleY = scale, alpha = alpha)
     }
 
+@ExperimentalMaterial3Api
+private fun CacheDrawScope.drawCaretWithPath(
+    density: Density,
+    windowContainerWidthInPx: Int,
+    containerColor: Color,
+    caretSize: DpSize,
+    anchorLayoutCoordinates: LayoutCoordinates?
+): DrawResult {
+    val path = Path()
+
+    if (anchorLayoutCoordinates != null) {
+        val caretHeightPx: Int
+        val caretWidthPx: Int
+        val screenWidthPx: Int
+        val tooltipAnchorSpacing: Int
+        with(density) {
+            caretHeightPx = caretSize.height.roundToPx()
+            caretWidthPx = caretSize.width.roundToPx()
+            screenWidthPx = windowContainerWidthInPx
+            tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
+        }
+        val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
+        val anchorLeft = anchorBounds.left
+        val anchorRight = anchorBounds.right
+        val anchorTop = anchorBounds.top
+        val anchorMid = (anchorRight + anchorLeft) / 2
+        val anchorWidth = anchorRight - anchorLeft
+        val tooltipWidth = this.size.width
+        val tooltipHeight = this.size.height
+        val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
+        val caretY =
+            if (isCaretTop) {
+                0f
+            } else {
+                tooltipHeight
+            }
+
+        // Default the caret to be in the middle
+        // caret might need to be offset depending on where
+        // the tooltip is placed relative to the anchor
+        var position: Offset =
+            if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
+                Offset(anchorMid, caretY)
+            } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
+                val anchorMidFromRightScreenEdge = screenWidthPx - anchorMid
+                val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+                Offset(caretX, caretY)
+            } else {
+                Offset(tooltipWidth / 2, caretY)
+            }
+        if (anchorMid - tooltipWidth / 2 < 0) {
+            // The tooltip needs to be start aligned if it would collide with the left side of
+            // screen.
+            position = Offset(anchorMid - anchorLeft, caretY)
+        } else if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
+            // The tooltip needs to be end aligned if it would collide with the right side of the
+            // screen.
+            position = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
+        }
+
+        if (isCaretTop) {
+            path.apply {
+                moveTo(x = position.x, y = position.y)
+                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+                lineTo(x = position.x, y = position.y - caretHeightPx)
+                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+                close()
+            }
+        } else {
+            path.apply {
+                moveTo(x = position.x, y = position.y)
+                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+                lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
+                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+                close()
+            }
+        }
+    }
+
+    return onDrawWithContent {
+        if (anchorLayoutCoordinates != null) {
+            drawContent()
+            drawPath(path = path, color = containerColor)
+        }
+    }
+}
+
+@Composable internal expect fun windowContainerWidthInPx(): Int
+
 internal val SpacingBetweenTooltipAndAnchor = 4.dp
 internal val TooltipMinHeight = 24.dp
 internal val TooltipMinWidth = 40.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/BasicTooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/BasicTooltip.kt
index a9114ca..19d6004 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/BasicTooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/BasicTooltip.kt
@@ -21,17 +21,41 @@
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.foundation.layout.Box
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.TooltipState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
+import androidx.compose.ui.window.PopupProperties
 import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
@@ -58,7 +82,7 @@
  * @param content the composable that the tooltip will anchor to.
  */
 @Composable
-internal expect fun BasicTooltipBox(
+internal fun BasicTooltipBox(
     positionProvider: PopupPositionProvider,
     tooltip: @Composable () -> Unit,
     state: TooltipState,
@@ -67,7 +91,174 @@
     focusable: Boolean = true,
     enableUserInput: Boolean = true,
     content: @Composable () -> Unit
-)
+) {
+    val scope = rememberCoroutineScope()
+    Box {
+        if (state.isVisible) {
+            TooltipPopup(
+                positionProvider = positionProvider,
+                state = state,
+                onDismissRequest = onDismissRequest,
+                scope = scope,
+                focusable = focusable,
+                content = tooltip
+            )
+        }
+
+        WrappedAnchor(
+            enableUserInput = enableUserInput,
+            state = state,
+            modifier = modifier,
+            content = content
+        )
+    }
+
+    DisposableEffect(state) { onDispose { state.onDispose() } }
+}
+
+@Composable
+private fun WrappedAnchor(
+    enableUserInput: Boolean,
+    state: TooltipState,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit
+) {
+    val scope = rememberCoroutineScope()
+    val longPressLabel = BasicTooltipStrings.label()
+    Box(
+        modifier =
+            modifier
+                .handleGestures(enableUserInput, state)
+                .anchorSemantics(longPressLabel, enableUserInput, state, scope)
+    ) {
+        content()
+    }
+}
+
+@Composable
+private fun TooltipPopup(
+    positionProvider: PopupPositionProvider,
+    state: TooltipState,
+    onDismissRequest: (() -> Unit)?,
+    scope: CoroutineScope,
+    focusable: Boolean,
+    content: @Composable () -> Unit
+) {
+    val tooltipDescription = BasicTooltipStrings.description()
+    Popup(
+        popupPositionProvider = positionProvider,
+        onDismissRequest = {
+            if (onDismissRequest == null) {
+                if (state.isVisible) {
+                    scope.launch { state.dismiss() }
+                }
+            } else {
+                onDismissRequest()
+            }
+        },
+        properties = PopupProperties(focusable = focusable)
+    ) {
+        Box(
+            modifier =
+                Modifier.semantics {
+                    liveRegion = LiveRegionMode.Assertive
+                    paneTitle = tooltipDescription
+                }
+        ) {
+            content()
+        }
+    }
+}
+
+private fun Modifier.handleGestures(enabled: Boolean, state: TooltipState): Modifier =
+    if (enabled) {
+        this.pointerInput(state) {
+                coroutineScope {
+                    awaitEachGesture {
+                        // Long press will finish before or after show so keep track of it, in a
+                        // flow to handle both cases
+                        val isLongPressedFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
+                        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+                        val pass = PointerEventPass.Initial
+                        // wait for the first down press
+                        val inputType = awaitFirstDown(pass = pass).type
+
+                        if (inputType == PointerType.Touch || inputType == PointerType.Stylus) {
+                            try {
+                                // listen to if there is up gesture
+                                // within the longPressTimeout limit
+                                withTimeout(longPressTimeout) {
+                                    waitForUpOrCancellation(pass = pass)
+                                }
+                            } catch (_: PointerEventTimeoutCancellationException) {
+                                // handle long press - Show the tooltip
+                                launch(start = CoroutineStart.UNDISPATCHED) {
+                                    try {
+                                        isLongPressedFlow.tryEmit(true)
+                                        state.show(MutatePriority.PreventUserInput)
+                                    } finally {
+                                        if (state.isVisible) {
+                                            isLongPressedFlow.collectLatest { isLongPressed ->
+                                                if (!isLongPressed) {
+                                                    state.dismiss()
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+
+                                // consume the children's click handling
+                                // Long press may still be in progress
+                                val upEvent = waitForUpOrCancellation(pass = pass)
+                                upEvent?.consume()
+                            } finally {
+                                isLongPressedFlow.tryEmit(false)
+                            }
+                        }
+                    }
+                }
+            }
+            .pointerInput(state) {
+                coroutineScope {
+                    awaitPointerEventScope {
+                        val pass = PointerEventPass.Main
+
+                        while (true) {
+                            val event = awaitPointerEvent(pass)
+                            val inputType = event.changes[0].type
+                            if (inputType == PointerType.Mouse) {
+                                when (event.type) {
+                                    PointerEventType.Enter -> {
+                                        launch { state.show(MutatePriority.UserInput) }
+                                    }
+                                    PointerEventType.Exit -> {
+                                        state.dismiss()
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+    } else this
+
+private fun Modifier.anchorSemantics(
+    label: String,
+    enabled: Boolean,
+    state: TooltipState,
+    scope: CoroutineScope
+): Modifier =
+    if (enabled) {
+        this.parentSemantics {
+            onLongClick(
+                label = label,
+                action = {
+                    scope.launch { state.show() }
+                    true
+                }
+            )
+        }
+    } else this
 
 /**
  * Create and remember the default [BasicTooltipState].
@@ -186,3 +377,10 @@
      */
     const val TooltipDuration = 1500L
 }
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+internal expect object BasicTooltipStrings {
+    @Composable fun label(): String
+
+    @Composable fun description(): String
+}
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/NavigationDrawer.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/NavigationDrawer.commonStubs.kt
deleted file mode 100644
index 39fe5a5..0000000
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/NavigationDrawer.commonStubs.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material3
-
-import androidx.compose.runtime.Composable
-
-@Composable
-internal actual fun DrawerPredictiveBackHandler(
-    drawerState: DrawerState,
-    content: @Composable (DrawerPredictiveBackState) -> Unit
-): Unit = implementedInJetBrainsFork()
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/Tooltip.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/Tooltip.commonStubs.kt
index 6c8027d..72f9b6f 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/Tooltip.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/Tooltip.commonStubs.kt
@@ -17,64 +17,5 @@
 package androidx.compose.material3
 
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpSize
 
-@Composable
-@ExperimentalMaterial3Api
-actual fun TooltipScope.PlainTooltip(
-    modifier: Modifier,
-    caretSize: DpSize,
-    shape: Shape,
-    contentColor: Color,
-    containerColor: Color,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
-    content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
-
-@Composable
-@ExperimentalMaterial3Api
-actual fun TooltipScope.PlainTooltip(
-    modifier: Modifier,
-    caretSize: DpSize,
-    maxWidth: Dp,
-    shape: Shape,
-    contentColor: Color,
-    containerColor: Color,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
-    content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
-
-@Composable
-@ExperimentalMaterial3Api
-actual fun TooltipScope.RichTooltip(
-    modifier: Modifier,
-    title: (@Composable () -> Unit)?,
-    action: (@Composable () -> Unit)?,
-    caretSize: DpSize,
-    shape: Shape,
-    colors: RichTooltipColors,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
-    text: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
-
-@Composable
-@ExperimentalMaterial3Api
-actual fun TooltipScope.RichTooltip(
-    modifier: Modifier,
-    title: (@Composable () -> Unit)?,
-    action: (@Composable () -> Unit)?,
-    caretSize: DpSize,
-    maxWidth: Dp,
-    shape: Shape,
-    colors: RichTooltipColors,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
-    text: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
+@Composable internal actual fun windowContainerWidthInPx(): Int = implementedInJetBrainsFork()
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/BasicTooltip.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/BasicTooltip.commonStubs.kt
index 89c7499..cd96c5f 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/BasicTooltip.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/BasicTooltip.commonStubs.kt
@@ -16,22 +16,12 @@
 
 package androidx.compose.material3.internal
 
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.TooltipState
 import androidx.compose.material3.implementedInJetBrainsFork
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.window.PopupPositionProvider
 
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal actual fun BasicTooltipBox(
-    positionProvider: PopupPositionProvider,
-    tooltip: @Composable () -> Unit,
-    state: TooltipState,
-    modifier: Modifier,
-    onDismissRequest: (() -> Unit)?,
-    focusable: Boolean,
-    enableUserInput: Boolean,
-    content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+internal actual object BasicTooltipStrings {
+    @Composable actual fun label(): String = implementedInJetBrainsFork()
+
+    @Composable actual fun description(): String = implementedInJetBrainsFork()
+}
diff --git a/development/plot-benchmarks/.nvmrc b/development/plot-benchmarks/.nvmrc
index 238155b..2f68077 100644
--- a/development/plot-benchmarks/.nvmrc
+++ b/development/plot-benchmarks/.nvmrc
@@ -1 +1 @@
-v20.12.2
\ No newline at end of file
+v22.12.0
\ No newline at end of file
diff --git a/development/plot-benchmarks/index.html b/development/plot-benchmarks/index.html
index 0e19900..ca577dc 100644
--- a/development/plot-benchmarks/index.html
+++ b/development/plot-benchmarks/index.html
@@ -5,7 +5,7 @@
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <title>Plot Benchmarks</title>
-  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
 </head>
 
 <body>
diff --git a/development/plot-benchmarks/package-lock.json b/development/plot-benchmarks/package-lock.json
index c6f8262..2f5a92f 100644
--- a/development/plot-benchmarks/package-lock.json
+++ b/development/plot-benchmarks/package-lock.json
@@ -8,16 +8,16 @@
       "name": "plot-benchmarks",
       "version": "0.2.0",
       "dependencies": {
-        "chart.js": "^4.4.6",
+        "chart.js": "^4.4.7",
         "comlink": "4.4.2"
       },
       "devDependencies": {
         "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
         "@tsconfig/svelte": "^5.0.4",
-        "svelte": "^5.1.12",
-        "svelte-check": "^4.0.5",
+        "svelte": "^5.16.1",
+        "svelte-check": "^4.1.1",
         "tslib": "^2.8.1",
-        "typescript": "^5.6.3",
+        "typescript": "^5.7.2",
         "vite": "^5.4.10"
       }
     },
@@ -758,7 +758,7 @@
         "vite": "^5.0.0"
       }
     },
-    "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+    "node_modules/@sveltejs/vite-plugin-svelte/node_modules/@sveltejs/vite-plugin-svelte-inspector": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
       "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
@@ -834,9 +834,9 @@
       }
     },
     "node_modules/chart.js": {
-      "version": "4.4.6",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
-      "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
+      "version": "4.4.7",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
+      "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
       "license": "MIT",
       "dependencies": {
         "@kurkle/color": "^0.3.0"
@@ -861,6 +861,16 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/comlink": {
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz",
@@ -935,21 +945,20 @@
       }
     },
     "node_modules/esm-env": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz",
-      "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
+      "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
       "dev": true,
       "license": "MIT"
     },
     "node_modules/esrap": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
-      "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.3.2.tgz",
+      "integrity": "sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@jridgewell/sourcemap-codec": "^1.4.15",
-        "@types/estree": "^1.0.1"
+        "@jridgewell/sourcemap-codec": "^1.4.15"
       }
     },
     "node_modules/fdir": {
@@ -983,13 +992,13 @@
       }
     },
     "node_modules/is-reference": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
-      "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+      "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@types/estree": "*"
+        "@types/estree": "^1.0.6"
       }
     },
     "node_modules/kleur": {
@@ -1035,9 +1044,9 @@
       "license": "MIT"
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
       "dev": true,
       "funding": [
         {
@@ -1061,9 +1070,9 @@
       "license": "ISC"
     },
     "node_modules/postcss": {
-      "version": "8.4.47",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
-      "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+      "version": "8.4.49",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
       "dev": true,
       "funding": [
         {
@@ -1082,7 +1091,7 @@
       "license": "MIT",
       "dependencies": {
         "nanoid": "^3.3.7",
-        "picocolors": "^1.1.0",
+        "picocolors": "^1.1.1",
         "source-map-js": "^1.2.1"
       },
       "engines": {
@@ -1164,9 +1173,9 @@
       }
     },
     "node_modules/svelte": {
-      "version": "5.1.12",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.12.tgz",
-      "integrity": "sha512-U9BwbSybb9QAKAHg4hl61hVBk97U2QjUKmZa5++QEGoi6Nml6x6cC9KmNT1XObGawToN3DdLpdCs/Z5Yl5IXjQ==",
+      "version": "5.16.1",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.16.1.tgz",
+      "integrity": "sha512-FsA1OjAKMAFSDob6j/Tv2ZV9rY4SeqPd1WXQlQkFkePAozSHLp6tbkU9qa1xJ+uTRzMSM2Vx3USdsYZBXd3H3g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1177,9 +1186,10 @@
         "acorn-typescript": "^1.4.13",
         "aria-query": "^5.3.1",
         "axobject-query": "^4.1.0",
-        "esm-env": "^1.0.0",
-        "esrap": "^1.2.2",
-        "is-reference": "^3.0.2",
+        "clsx": "^2.1.1",
+        "esm-env": "^1.2.1",
+        "esrap": "^1.3.2",
+        "is-reference": "^3.0.3",
         "locate-character": "^3.0.0",
         "magic-string": "^0.30.11",
         "zimmerframe": "^1.1.2"
@@ -1189,9 +1199,9 @@
       }
     },
     "node_modules/svelte-check": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.5.tgz",
-      "integrity": "sha512-icBTBZ3ibBaywbXUat3cK6hB5Du+Kq9Z8CRuyLmm64XIe2/r+lQcbuBx/IQgsbrC+kT2jQ0weVpZSSRIPwB6jQ==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.1.tgz",
+      "integrity": "sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1220,9 +1230,9 @@
       "license": "0BSD"
     },
     "node_modules/typescript": {
-      "version": "5.6.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
-      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+      "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
       "dev": true,
       "license": "Apache-2.0",
       "bin": {
@@ -1234,9 +1244,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.4.10",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
-      "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
+      "version": "5.4.11",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+      "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1674,15 +1684,17 @@
         "kleur": "^4.1.5",
         "magic-string": "^0.30.12",
         "vitefu": "^1.0.3"
-      }
-    },
-    "@sveltejs/vite-plugin-svelte-inspector": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
-      "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
-      "dev": true,
-      "requires": {
-        "debug": "^4.3.7"
+      },
+      "dependencies": {
+        "@sveltejs/vite-plugin-svelte-inspector": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
+          "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
+          "dev": true,
+          "requires": {
+            "debug": "^4.3.7"
+          }
+        }
       }
     },
     "@tsconfig/svelte": {
@@ -1723,9 +1735,9 @@
       "dev": true
     },
     "chart.js": {
-      "version": "4.4.6",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
-      "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
+      "version": "4.4.7",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
+      "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
       "requires": {
         "@kurkle/color": "^0.3.0"
       }
@@ -1739,6 +1751,12 @@
         "readdirp": "^4.0.1"
       }
     },
+    "clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "dev": true
+    },
     "comlink": {
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz",
@@ -1791,19 +1809,18 @@
       }
     },
     "esm-env": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz",
-      "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
+      "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
       "dev": true
     },
     "esrap": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
-      "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.3.2.tgz",
+      "integrity": "sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==",
       "dev": true,
       "requires": {
-        "@jridgewell/sourcemap-codec": "^1.4.15",
-        "@types/estree": "^1.0.1"
+        "@jridgewell/sourcemap-codec": "^1.4.15"
       }
     },
     "fdir": {
@@ -1821,12 +1838,12 @@
       "optional": true
     },
     "is-reference": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
-      "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+      "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
       "dev": true,
       "requires": {
-        "@types/estree": "*"
+        "@types/estree": "^1.0.6"
       }
     },
     "kleur": {
@@ -1863,9 +1880,9 @@
       "dev": true
     },
     "nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
       "dev": true
     },
     "picocolors": {
@@ -1875,13 +1892,13 @@
       "dev": true
     },
     "postcss": {
-      "version": "8.4.47",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
-      "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+      "version": "8.4.49",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
       "dev": true,
       "requires": {
         "nanoid": "^3.3.7",
-        "picocolors": "^1.1.0",
+        "picocolors": "^1.1.1",
         "source-map-js": "^1.2.1"
       }
     },
@@ -1935,9 +1952,9 @@
       "dev": true
     },
     "svelte": {
-      "version": "5.1.12",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.12.tgz",
-      "integrity": "sha512-U9BwbSybb9QAKAHg4hl61hVBk97U2QjUKmZa5++QEGoi6Nml6x6cC9KmNT1XObGawToN3DdLpdCs/Z5Yl5IXjQ==",
+      "version": "5.16.1",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.16.1.tgz",
+      "integrity": "sha512-FsA1OjAKMAFSDob6j/Tv2ZV9rY4SeqPd1WXQlQkFkePAozSHLp6tbkU9qa1xJ+uTRzMSM2Vx3USdsYZBXd3H3g==",
       "dev": true,
       "requires": {
         "@ampproject/remapping": "^2.3.0",
@@ -1947,18 +1964,19 @@
         "acorn-typescript": "^1.4.13",
         "aria-query": "^5.3.1",
         "axobject-query": "^4.1.0",
-        "esm-env": "^1.0.0",
-        "esrap": "^1.2.2",
-        "is-reference": "^3.0.2",
+        "clsx": "^2.1.1",
+        "esm-env": "^1.2.1",
+        "esrap": "^1.3.2",
+        "is-reference": "^3.0.3",
         "locate-character": "^3.0.0",
         "magic-string": "^0.30.11",
         "zimmerframe": "^1.1.2"
       }
     },
     "svelte-check": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.5.tgz",
-      "integrity": "sha512-icBTBZ3ibBaywbXUat3cK6hB5Du+Kq9Z8CRuyLmm64XIe2/r+lQcbuBx/IQgsbrC+kT2jQ0weVpZSSRIPwB6jQ==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.1.tgz",
+      "integrity": "sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==",
       "dev": true,
       "requires": {
         "@jridgewell/trace-mapping": "^0.3.25",
@@ -1975,15 +1993,15 @@
       "dev": true
     },
     "typescript": {
-      "version": "5.6.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
-      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+      "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
       "dev": true
     },
     "vite": {
-      "version": "5.4.10",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
-      "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
+      "version": "5.4.11",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+      "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
       "dev": true,
       "requires": {
         "esbuild": "^0.21.3",
diff --git a/development/plot-benchmarks/package.json b/development/plot-benchmarks/package.json
index 0a30e86..73bf40c 100644
--- a/development/plot-benchmarks/package.json
+++ b/development/plot-benchmarks/package.json
@@ -12,14 +12,14 @@
   "devDependencies": {
     "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
     "@tsconfig/svelte": "^5.0.4",
-    "svelte": "^5.1.12",
-    "svelte-check": "^4.0.5",
+    "svelte": "^5.16.1",
+    "svelte-check": "^4.1.1",
     "tslib": "^2.8.1",
-    "typescript": "^5.6.3",
+    "typescript": "^5.7.2",
     "vite": "^5.4.10"
   },
   "dependencies": {
-    "chart.js": "^4.4.6",
+    "chart.js": "^4.4.7",
     "comlink": "4.4.2"
   }
 }
\ No newline at end of file
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 5cb1563..ceaad52 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -563,6 +563,64 @@
     }
 
     /**
+     * {@code webp_without_exif_trailing_data.webp} contains the same data as {@code
+     * webp_without_exif.webp} with {@code 0xDEADBEEFDEADBEEF} appended on the end (but excluded
+     * from the RIFF length).
+     *
+     * <p>This test ensures the resulting file is valid (i.e. the trailing data is still excluded
+     * from RIFF length).
+     */
+    // https://issuetracker.google.com/385766064
+    @Test
+    @LargeTest
+    public void testWebpWithoutExifAndTrailingData() throws Throwable {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.webp_without_exif_trailing_data,
+                        "webp_without_exif_trailing_data.webp");
+        testWritingExif(imageFile, /* expectedAttributes= */ null);
+    }
+
+    /**
+     * {@code webp_without_exif_trailing_data.webp} contains the same data as {@code
+     * webp_without_exif.webp} with {@code 0xDEADBEEFDEADBEEF} appended on the end (but excluded
+     * from the RIFF length).
+     *
+     * <p>This test ensures the trailing data is preserved.
+     */
+    // https://issuetracker.google.com/385766064
+    @Test
+    @LargeTest
+    public void testWebpWithoutExifAndTrailingData_trailingDataPreserved() throws Throwable {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.webp_without_exif_trailing_data,
+                        "webp_without_exif_trailing_data.webp");
+
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.saveAttributes();
+
+        byte[] imageData = Files.toByteArray(imageFile);
+        byte[] expectedTrailingData =
+                new byte[] {
+                    (byte) 0xDE,
+                    (byte) 0xAD,
+                    (byte) 0xBE,
+                    (byte) 0xEF,
+                    (byte) 0xDE,
+                    (byte) 0xAD,
+                    (byte) 0xBE,
+                    (byte) 0xEF
+                };
+        byte[] actualTrailingData =
+                Arrays.copyOfRange(
+                        imageData,
+                        imageData.length - expectedTrailingData.length,
+                        imageData.length);
+        assertThat(actualTrailingData).isEqualTo(expectedTrailingData);
+    }
+
+    /**
      * Support for retrieving EXIF from HEIC was added in SDK 28.
      */
     @Test
diff --git a/exifinterface/exifinterface/src/androidTest/res/raw/webp_without_exif_trailing_data.webp b/exifinterface/exifinterface/src/androidTest/res/raw/webp_without_exif_trailing_data.webp
new file mode 100644
index 0000000..1b4d584
--- /dev/null
+++ b/exifinterface/exifinterface/src/androidTest/res/raw/webp_without_exif_trailing_data.webp
Binary files differ
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 857e673..17da6076 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -6749,8 +6749,8 @@
 
         // WebP signature
         copy(totalInputStream, totalOutputStream, WEBP_SIGNATURE_1.length);
-        // File length will be written after all the chunks have been written
-        totalInputStream.skipFully(WEBP_FILE_SIZE_BYTE_LENGTH + WEBP_SIGNATURE_2.length);
+        int riffLength = totalInputStream.readInt();
+        totalInputStream.skipFully(WEBP_SIGNATURE_2.length);
 
         // Create a separate byte array to calculate file length
         ByteArrayOutputStream nonHeaderByteArrayOutputStream = null;
@@ -6925,8 +6925,9 @@
                 }
             }
 
-            // Copy the rest of the file
-            copy(totalInputStream, nonHeaderOutputStream);
+            // Copy the rest of the RIFF part of the file
+            int remainingRiffBytes = riffLength + 8 - totalInputStream.position();
+            copy(totalInputStream, nonHeaderOutputStream, remainingRiffBytes);
 
             // Write file length + second signature
             totalOutputStream.writeInt(nonHeaderByteArrayOutputStream.size()
@@ -6936,6 +6937,8 @@
                 mOffsetToExifData = totalOutputStream.mOutputStream.size() + exifOffset;
             }
             nonHeaderByteArrayOutputStream.writeTo(totalOutputStream);
+            // Copy any non-RIFF trailing data
+            copy(totalInputStream, totalOutputStream);
         } catch (Exception e) {
             throw new IOException("Failed to save WebP file", e);
         } finally {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ff3331b..510a96e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -61,6 +61,9 @@
 kotlinToolingCore = "1.9.24"
 ksp = "2.1.0-1.0.29"
 ktfmt = "0.50"
+# Version format is: 1.KOTLIN_MAJOR_VERSION.0.KTFMT_VERSION
+# When updated, the id and checksum in StudioTask needs to be updated too
+ktfmtIdeaPlugin = "1.1.0.50"
 leakcanary = "2.13"
 media3 = "1.4.1"
 metalava = "1.0.0-alpha12"
diff --git a/libraryversions.toml b/libraryversions.toml
index bee8c21..0b26c3d 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -13,11 +13,11 @@
 BLUETOOTH = "1.0.0-alpha02"
 BROWSER = "1.9.0-alpha01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.5.0-alpha04"
-CAMERA_MEDIA3 = "1.0.0-alpha01"
+CAMERA = "1.5.0-alpha05"
+CAMERA_MEDIA3 = "1.0.0-alpha02"
 CAMERA_PIPE = "1.0.0-alpha01"
 CAMERA_TESTING = "1.0.0-alpha01"
-CAMERA_VIEWFINDER = "1.4.0-alpha11"
+CAMERA_VIEWFINDER = "1.4.0-alpha12"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-beta03"
 COLLECTION = "1.5.0-beta01"
@@ -161,18 +161,18 @@
 VIEWPAGER = "1.1.0-rc01"
 VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.5.0-alpha07"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha30"
+WEAR_COMPOSE = "1.5.0-alpha08"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha31"
 WEAR_CORE = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha02"
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha05"
-WEAR_PROTOLAYOUT = "1.3.0-alpha05"
+WEAR_PROTOLAYOUT = "1.3.0-alpha06"
 WEAR_REMOTE_INTERACTIONS = "1.1.0-rc01"
-WEAR_TILES = "1.5.0-alpha05"
+WEAR_TILES = "1.5.0-alpha06"
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
-WEAR_WATCHFACE = "1.3.0-alpha04"
+WEAR_WATCHFACE = "1.3.0-alpha05"
 WEBKIT = "1.13.0-alpha03"
 # Adding a comment to prevent merge conflicts for Window artifact
 WINDOW = "1.4.0-beta01"
diff --git a/room/benchmark/build.gradle b/room/benchmark/build.gradle
index 19362ae..d027d93 100644
--- a/room/benchmark/build.gradle
+++ b/room/benchmark/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -54,4 +56,9 @@
 
 androidx {
     type = LibraryType.BENCHMARK
+    kotlinTarget = KotlinTarget.KOTLIN_2_0
+}
+
+ksp {
+    useKsp2 = true
 }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
index 1ba2f01..929ee4d 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
@@ -75,8 +75,7 @@
 import kotlin.math.abs
 import kotlinx.coroutines.delay
 import org.junit.Rule
-
-// import org.junit.Test
+import org.junit.Test
 
 private const val delayBetweenItems = 2500L
 private const val animationTime = 900L
@@ -85,7 +84,7 @@
 class CarouselTest {
     @get:Rule val rule = createComposeRule()
 
-    // @Test
+    @Test
     fun carousel_autoScrolls() {
         rule.setContent { SampleCarousel { BasicText(text = "Text ${it + 1}") } }
 
@@ -98,7 +97,7 @@
         rule.onNodeWithText("Text 3").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_onFocus_stopsScroll() {
         rule.setContent { SampleCarousel { BasicText(text = "Text ${it + 1}") } }
 
@@ -113,7 +112,7 @@
         rule.onNodeWithText("Text 1").onParent().assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_onUserTriggeredPause_stopsScroll() {
         rule.setContent {
             val carouselState = rememberCarouselState()
@@ -132,7 +131,7 @@
         rule.onNodeWithText("Text 1").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_onUserTriggeredPauseAndResume_resumeScroll() {
         var pauseHandle: ScrollPauseHandle? = null
         rule.setContent {
@@ -163,7 +162,7 @@
         rule.onNodeWithText("Text 2").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_onMultipleUserTriggeredPauseAndResume_resumesScroll() {
         var pauseHandle1: ScrollPauseHandle? = null
         var pauseHandle2: ScrollPauseHandle? = null
@@ -207,7 +206,7 @@
         rule.onNodeWithText("Text 2").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_onRepeatedResumesOnSamePauseHandle_ignoresSubsequentResumeCalls() {
         var pauseHandle1: ScrollPauseHandle? = null
         rule.setContent {
@@ -246,7 +245,7 @@
         rule.onNodeWithText("Text 1").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_outOfFocus_resumesScroll() {
         rule.setContent {
             Column {
@@ -265,14 +264,14 @@
         rule.onNodeWithText("Text 2").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_pagerIndicatorDisplayed() {
         rule.setContent { SampleCarousel { SampleCarouselItem(index = it) } }
 
         rule.onNodeWithTag("indicator").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_withAnimatedContent_successfulTransition() {
         rule.setContent {
             SampleCarousel {
@@ -292,7 +291,7 @@
         rule.onNodeWithText("PLAY").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_withAnimatedContent_successfulFocusIn() {
         rule.setContent { SampleCarousel { SampleCarouselItem(index = it) } }
 
@@ -307,7 +306,7 @@
         rule.onNodeWithText("Play 0", useUnmergedTree = true).assertIsDisplayed().assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_parentContainerGainsFocus_onBackPress() {
         rule.setContent {
             Box(modifier = Modifier.testTag("box-container").fillMaxSize().focusable()) {
@@ -336,7 +335,7 @@
         rule.onNodeWithTag("box-container").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_withCarouselItem_parentContainerGainsFocusOnBackPress() {
         rule.setContent {
             Box(modifier = Modifier.testTag("box-container").fillMaxSize().focusable()) {
@@ -367,7 +366,7 @@
         rule.onNodeWithTag("box-container").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_scrollToRegainFocus_checkBringIntoView() {
         val focusRequester = FocusRequester()
         rule.setContent {
@@ -453,7 +452,7 @@
         assertThat(checkNodeCompletelyVisible(rule, "featured-carousel")).isTrue()
     }
 
-    // @Test
+    @Test
     fun carousel_zeroItemCount_shouldNotCrash() {
         val testTag = "emptyCarousel"
         rule.setContent { Carousel(itemCount = 0, modifier = Modifier.testTag(testTag)) {} }
@@ -461,7 +460,7 @@
         rule.onNodeWithTag(testTag).assertExists()
     }
 
-    // @Test
+    @Test
     fun carousel_oneItemCount_shouldNotCrash() {
         val testTag = "emptyCarousel"
         rule.setContent { Carousel(itemCount = 1, modifier = Modifier.testTag(testTag)) {} }
@@ -469,7 +468,7 @@
         rule.onNodeWithTag(testTag).assertExists()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingWithFocusableItemsOnTop_focusStaysWithinCarousel() {
         rule.setContent {
             Column {
@@ -521,7 +520,7 @@
         rule.onNodeWithText("Button-1").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingFastMultipleKeyPresses_focusStaysWithinCarousel() {
         val carouselState = CarouselState()
         val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
@@ -585,7 +584,7 @@
         rule.onNodeWithText("Play ${finalItem + 3}", useUnmergedTree = true).assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingDpadLongPress_moveOnlyOneSlide() {
         rule.setContent {
             SampleCarousel(itemCount = 6) { index -> SampleButton("Button ${index + 1}") }
@@ -627,7 +626,7 @@
         rule.onNodeWithText("Button 1").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingLtr_RightMovesToNextSlideLeftMovesToPrevSlide() {
         rule.setContent { SampleCarousel { index -> SampleButton("Button ${index + 1}") } }
 
@@ -668,7 +667,7 @@
         rule.onNodeWithText("Button 1").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingRtl_LeftMovesToNextSlideRightMovesToPrevSlide() {
         rule.setContent {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
@@ -712,7 +711,7 @@
         rule.onNodeWithText("Button 1").assertIsDisplayed()
     }
 
-    // @Test
+    @Test
     fun carousel_itemCountChangesDuringAnimation_shouldNotCrash() {
         val itemDisplayDurationMs: Long = 100
         var itemChanges = 0
@@ -745,7 +744,7 @@
         rule.waitUntil(timeoutMillis = 5000) { itemChanges > minSuccessfulItemChanges }
     }
 
-    // @Test
+    @Test
     fun carousel_slideWithTwoButtonsInARow_focusMovesWithinSlideAndChangesSlideOnlyOnFocusExit() {
         rule.setContent {
             // No AutoScrolling
@@ -766,7 +765,7 @@
         rule.onNodeWithText("Left Button 2").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingLtr_loopsAroundWhenNoAdjacentFocusableItemsArePresent() {
         rule.setContent {
             // No AutoScrolling
@@ -788,7 +787,7 @@
         rule.onNodeWithText("Button-0").assertIsFocused()
     }
 
-    // @Test
+    @Test
     fun carousel_manualScrollingLtr_focusMovesToAdjacentItemsOutsideCarousel() {
         rule.setContent {
             val focusRequester = remember { FocusRequester() }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
index 572fbfe..3dfbd42 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
@@ -16,28 +16,42 @@
 
 package androidx.tv.material3
 
-// @OptIn(ExperimentalFoundationApi::class)
-// internal fun Modifier.bringIntoViewIfChildrenAreFocused(): Modifier = composed(
-//     inspectorInfo = debugInspectorInfo { name = "bringIntoViewIfChildrenAreFocused" },
-//     factory = {
-//         var myRect: Rect = Rect.Zero
-//         this
-//             .onSizeChanged {
-//                 myRect = Rect(Offset.Zero, Offset(it.width.toFloat(), it.height.toFloat()))
-//             }
-//            .bringIntoViewResponder(
-//                remember {
-//                    object : BringIntoViewResponder {
-//                        // return the current rectangle and ignoring the child rectangle received.
-//                        @ExperimentalFoundationApi
-//                        override fun calculateRectForParent(localRect: Rect): Rect = myRect
-//
-//                        // The container is not expected to be scrollable. Hence the child is
-//                        // already in view with respect to the container.
-//                        @ExperimentalFoundationApi
-//                        override suspend fun bringChildIntoView(localRect: () -> Rect?) {}
-//                    }
-//                }
-//            )
-//     }
-// )
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.relocation.bringIntoViewResponder
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.debugInspectorInfo
+
+// Suppressed the deprecation because BringIntoViewModifierNode is not
+// available in compose.ui 1.7.x
+@Suppress("DEPRECATION")
+@OptIn(ExperimentalFoundationApi::class)
+internal fun Modifier.bringIntoViewIfChildrenAreFocused(): Modifier =
+    composed(
+        inspectorInfo = debugInspectorInfo { name = "bringIntoViewIfChildrenAreFocused" },
+        factory = {
+            var myRect: Rect = Rect.Zero
+            this.onSizeChanged {
+                    myRect = Rect(Offset.Zero, Offset(it.width.toFloat(), it.height.toFloat()))
+                }
+                .bringIntoViewResponder(
+                    remember {
+                        object : androidx.compose.foundation.relocation.BringIntoViewResponder {
+                            // return the current rectangle and ignoring the child rectangle
+                            // received.
+                            @ExperimentalFoundationApi
+                            override fun calculateRectForParent(localRect: Rect): Rect = myRect
+
+                            // The container is not expected to be scrollable. Hence the child is
+                            // already in view with respect to the container.
+                            @ExperimentalFoundationApi
+                            override suspend fun bringChildIntoView(localRect: () -> Rect?) {}
+                        }
+                    }
+                )
+        }
+    )
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index e5ff9a8..da786e0 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -48,6 +48,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusDirection.Companion.Left
@@ -84,10 +85,6 @@
 /**
  * Composes a hero card rotator to highlight a piece of content.
  *
- * Note: The animations and focus management features have been dropped temporarily due to some
- * technical challenges. If you need them, consider using the previous version of the library
- * (1.0.0-alpha10) or kindly wait until the next alpha version (1.1.0-alpha01).
- *
  * Examples:
  *
  * @sample androidx.tv.material3.samples.SimpleCarousel
@@ -104,6 +101,7 @@
  * @param carouselIndicator indicator showing the position of the current item among all items.
  * @param content defines the items for a given index.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 @ExperimentalTvMaterial3Api
 @Composable
 fun Carousel(
@@ -145,14 +143,14 @@
         modifier =
             modifier
                 .carouselSemantics(itemCount = itemCount, state = carouselState)
-                // .bringIntoViewIfChildrenAreFocused()
+                .bringIntoViewIfChildrenAreFocused()
                 .focusRequester(carouselOuterBoxFocusRequester)
                 .onFocusChanged {
                     focusState = it
                     // When the carousel gains focus for the first time
-                    //            if (it.isFocused && isAutoScrollActive) {
-                    //                focusManager.moveFocus(FocusDirection.Enter)
-                    //            }
+                    if (it.isFocused && isAutoScrollActive) {
+                        focusManager.moveFocus(FocusDirection.Enter)
+                    }
                 }
                 .handleKeyEvents(
                     carouselState = carouselState,
@@ -184,7 +182,7 @@
                     // Outer box is focused
                     if (!isAutoScrollActive && focusState?.isFocused == true) {
                         carouselOuterBoxFocusRequester.requestFocus()
-                        //                        focusManager.moveFocus(FocusDirection.Enter)
+                        focusManager.moveFocus(FocusDirection.Enter)
                     }
                 }
             }
@@ -212,9 +210,8 @@
     return !accessibilityManager.isEnabled && !(carouselIsFocused || carouselHasFocus)
 }
 
-// @OptIn(ExperimentalAnimationApi::class)
 private suspend fun AnimatedVisibilityScope.onAnimationCompletion(action: suspend () -> Unit) {
-    //    snapshotFlow { transition.currentState == transition.targetState }.first { it }
+    snapshotFlow { transition.currentState == transition.targetState }.first { it }
     action.invoke()
 }
 
@@ -249,9 +246,7 @@
     onAutoScrollChange(doAutoScroll)
 }
 
-@OptIn(
-    ExperimentalTvMaterial3Api::class,
-)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
 private fun Modifier.handleKeyEvents(
     carouselState: CarouselState,
     outerBoxFocusRequester: FocusRequester,
@@ -306,7 +301,7 @@
                 // Ignore KeyUp action type
                 it.type == KeyUp -> KeyEventPropagation.ContinuePropagation
                 it.key == Key.Back -> {
-                    //            focusManager.moveFocus(FocusDirection.Exit)
+                    focusManager.moveFocus(FocusDirection.Exit)
                     KeyEventPropagation.ContinuePropagation
                 }
                 it.key == Key.DirectionLeft -> handledHorizontalFocusMove(Left)
@@ -316,14 +311,15 @@
         }
         .focusProperties {
             // allow exit along horizontal axis only for first and last slide.
-            //    exit = {
-            //        when {
-            //            shouldFocusExitCarousel(it, carouselState, itemCount, isLtr) ->
-            //                FocusRequester.Default
-            //
-            //            else -> FocusRequester.Cancel
-            //        }
-            //    }
+            // Suppressed the deprecation because onExit is not available in compose.ui 1.7.x
+            @Suppress("DEPRECATION")
+            exit = {
+                when {
+                    shouldFocusExitCarousel(it, carouselState, itemCount, isLtr) ->
+                        FocusRequester.Default
+                    else -> FocusRequester.Cancel
+                }
+            }
         }
 
 @OptIn(ExperimentalTvMaterial3Api::class)
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 9594347..20bb8e8 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -26,8 +26,8 @@
     defaultConfig {
         applicationId = "androidx.wear.compose.integration.demos"
         minSdk = 25
-        versionCode = 60
-        versionName = "1.60"
+        versionCode = 61
+        versionName = "1.61"
     }
 
     buildTypes {
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index 3ffc8fd..07fd4fb 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -46,9 +46,9 @@
   }
 
   public final class ButtonKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.types.LayoutColor? backgroundColor, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
   }
 
   public final class CardColors {
@@ -78,12 +78,12 @@
   }
 
   public final class CardKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement appCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? avatar, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? label, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.AppCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement card(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.types.LayoutColor? backgroundColor, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> content);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement appCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? avatar, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? label, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.AppCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement card(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> content);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement graphicDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> graphic, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional androidx.wear.protolayout.material3.GraphicDataCardStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryIcon, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.DataCardStyle style, optional int titleContentPlacement, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryText, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.DataCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement titleCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.TitleCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, optional int horizontalAlignment);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryIcon, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.DataCardStyle style, optional int titleContentPlacement, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryText, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.DataCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement titleCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TitleCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, optional int horizontalAlignment);
   }
 
   public final class ColorScheme {
@@ -209,8 +209,8 @@
   }
 
   public final class ImageKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional int contentScaleMode);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement backgroundImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.types.LayoutColor overlayColor, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayWidth, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayHeight, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional int contentScaleMode);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional int contentScaleMode);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement backgroundImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.types.LayoutColor overlayColor, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayWidth, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayHeight, optional int contentScaleMode);
   }
 
   @androidx.wear.protolayout.material3.MaterialScopeMarker public class MaterialScope {
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index 3ffc8fd..07fd4fb 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -46,9 +46,9 @@
   }
 
   public final class ButtonKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.types.LayoutColor? backgroundColor, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
   }
 
   public final class CardColors {
@@ -78,12 +78,12 @@
   }
 
   public final class CardKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement appCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? avatar, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? label, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.AppCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement card(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.types.LayoutColor? backgroundColor, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> content);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement appCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? avatar, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? label, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.AppCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement card(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> content);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement graphicDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> graphic, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional androidx.wear.protolayout.material3.GraphicDataCardStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryIcon, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.DataCardStyle style, optional int titleContentPlacement, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryText, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.DataCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement titleCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? background, optional androidx.wear.protolayout.material3.TitleCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, optional int horizontalAlignment);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryIcon, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.DataCardStyle style, optional int titleContentPlacement, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textDataCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryText, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.DataCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement titleCard(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> title, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? time, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.CardColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TitleCardStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding, optional int horizontalAlignment);
   }
 
   public final class ColorScheme {
@@ -209,8 +209,8 @@
   }
 
   public final class ImageKt {
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional int contentScaleMode);
-    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement backgroundImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.types.LayoutColor overlayColor, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayWidth, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayHeight, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional int contentScaleMode);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional int contentScaleMode);
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement backgroundImage(androidx.wear.protolayout.material3.MaterialScope, String protoLayoutResourceId, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension width, optional androidx.wear.protolayout.DimensionBuilders.ImageDimension height, optional androidx.wear.protolayout.types.LayoutColor overlayColor, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayWidth, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension overlayHeight, optional int contentScaleMode);
   }
 
   @androidx.wear.protolayout.material3.MaterialScopeMarker public class MaterialScope {
diff --git a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
index 36283be..618b67c 100644
--- a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
+++ b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
@@ -52,6 +52,8 @@
 import androidx.wear.protolayout.material3.textEdgeButton
 import androidx.wear.protolayout.material3.titleCard
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.backgroundColor
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.types.LayoutString
 import androidx.wear.protolayout.types.asLayoutConstraint
@@ -116,7 +118,7 @@
     }
 
 @Sampled
-fun topLeveLayout(
+fun topLevelLayout(
     context: Context,
     deviceConfiguration: DeviceParameters,
     clickable: Clickable
@@ -144,7 +146,7 @@
             },
             bottomSlot = {
                 iconEdgeButton(
-                    clickable,
+                    onClick = clickable,
                     modifier = LayoutModifier.contentDescription("Description")
                 ) {
                     icon("id")
@@ -164,10 +166,12 @@
             mainSlot = {
                 card(
                     onClick = clickable,
-                    modifier = LayoutModifier.contentDescription("Card with image background"),
+                    modifier =
+                        LayoutModifier.contentDescription("Card with image background")
+                            .clickable(id = "card"),
                     width = expand(),
                     height = expand(),
-                    background = { backgroundImage(protoLayoutResourceId = "id") }
+                    backgroundContent = { backgroundImage(protoLayoutResourceId = "id") }
                 ) {
                     text("Content of the Card!".layoutString)
                 }
@@ -314,10 +318,10 @@
                 button(
                     onClick = clickable,
                     modifier =
-                        LayoutModifier.contentDescription("Big button with image background"),
+                        LayoutModifier.contentDescription("Big button with image background")
+                            .backgroundColor(colorScheme.primary),
                     width = expand(),
                     height = expand(),
-                    backgroundColor = colorScheme.primary,
                     content = { text("Button!".layoutString) }
                 )
             }
@@ -392,7 +396,7 @@
                         LayoutModifier.contentDescription("Big button with image background"),
                     width = expand(),
                     height = expand(),
-                    background = { backgroundImage(protoLayoutResourceId = "id") }
+                    backgroundContent = { backgroundImage(protoLayoutResourceId = "id") }
                 )
             }
         )
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
index b7c09ff..fd7ae36 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
@@ -18,12 +18,10 @@
 
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.wear.protolayout.ActionBuilders
 import androidx.wear.protolayout.DeviceParametersBuilders
 import androidx.wear.protolayout.DimensionBuilders.expand
 import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.LayoutElementBuilders.Box
-import androidx.wear.protolayout.ModifiersBuilders
 import androidx.wear.protolayout.ModifiersBuilders.Background
 import androidx.wear.protolayout.ModifiersBuilders.Corner
 import androidx.wear.protolayout.ModifiersBuilders.Modifiers
@@ -36,6 +34,7 @@
 import androidx.wear.protolayout.material3.MaterialGoldenTest.Companion.pxToDp
 import androidx.wear.protolayout.material3.TitleContentPlacementInDataCard.Companion.Bottom
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.types.LayoutColor
 import androidx.wear.protolayout.types.layoutString
@@ -69,11 +68,7 @@
                 .setFontScale(1f)
                 .setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
                 .build()
-        val clickable: ModifiersBuilders.Clickable =
-            ModifiersBuilders.Clickable.Builder()
-                .setOnClick(ActionBuilders.LaunchAction.Builder().build())
-                .setId("action_id")
-                .build()
+        val clickable = clickable(id = "action_id")
         val testCases: HashMap<String, LayoutElementBuilders.LayoutElement> = HashMap()
 
         testCases["primarylayout_edgebuttonfilled_buttongroup_iconoverride_golden$goldenSuffix"] =
@@ -217,7 +212,9 @@
                             modifier = LayoutModifier.contentDescription("Card"),
                             width = expand(),
                             height = expand(),
-                            background = { backgroundImage(protoLayoutResourceId = IMAGE_ID) }
+                            backgroundContent = {
+                                backgroundImage(protoLayoutResourceId = IMAGE_ID)
+                            }
                         ) {
                             text(
                                 "Card with image background".layoutString,
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
index ac3e2cf..8fd28cf 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
@@ -23,15 +23,16 @@
 import androidx.wear.protolayout.ModifiersBuilders.Clickable
 import androidx.wear.protolayout.ModifiersBuilders.Corner
 import androidx.wear.protolayout.ModifiersBuilders.Padding
-import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING_DP
+import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING
 import androidx.wear.protolayout.material3.ButtonDefaults.IMAGE_BUTTON_DEFAULT_SIZE_DP
 import androidx.wear.protolayout.material3.ButtonDefaults.METADATA_TAG_BUTTON
 import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
 import androidx.wear.protolayout.material3.IconButtonStyle.Companion.defaultIconButtonStyle
 import androidx.wear.protolayout.material3.TextButtonStyle.Companion.defaultTextButtonStyle
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.background
+import androidx.wear.protolayout.modifiers.clip
 import androidx.wear.protolayout.modifiers.contentDescription
-import androidx.wear.protolayout.types.LayoutColor
 
 /**
  * Opinionated ProtoLayout Material3 icon button that offers a single slot to take content
@@ -55,7 +56,7 @@
  *   [ButtonDefaults.filledTonalButtonColors] and [ButtonDefaults.filledVariantButtonColors]. If
  *   using custom colors, it is important to choose a color pair from same role to ensure
  *   accessibility with sufficient color contrast.
- * @param background The background object to be used behind the content in the button. It is
+ * @param backgroundContent The background object to be used behind the content in the button. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified
  *   [ButtonColors.container] behind it.
@@ -78,18 +79,16 @@
     height: ContainerDimension = wrapWithMinTapTargetDimension(),
     shape: Corner = shapes.full,
     colors: ButtonColors = filledButtonColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: IconButtonStyle = defaultIconButtonStyle(),
-    contentPadding: Padding = Padding.Builder().setAll(DEFAULT_CONTENT_PADDING_DP.toDp()).build()
+    contentPadding: Padding = DEFAULT_CONTENT_PADDING
 ): LayoutElement =
     button(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(color = colors.container, corner = shape),
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = colors.container,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding,
         content = {
             withStyle(
@@ -122,7 +121,7 @@
  *   [ButtonDefaults.filledTonalButtonColors] and [ButtonDefaults.filledVariantButtonColors]. If
  *   using custom colors, it is important to choose a color pair from same role to ensure
  *   accessibility with sufficient color contrast.
- * @param background The background object to be used behind the content in the button. It is
+ * @param backgroundContent The background object to be used behind the content in the button. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified
  *   [ButtonColors.container] behind it.
@@ -146,18 +145,16 @@
     height: ContainerDimension = wrapWithMinTapTargetDimension(),
     shape: Corner = shapes.full,
     colors: ButtonColors = filledButtonColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: TextButtonStyle = defaultTextButtonStyle(),
-    contentPadding: Padding = Padding.Builder().setAll(DEFAULT_CONTENT_PADDING_DP.toDp()).build()
+    contentPadding: Padding = DEFAULT_CONTENT_PADDING
 ): LayoutElement =
     button(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(color = colors.container, corner = shape),
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = colors.container,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding,
         content = {
             withStyle(
@@ -184,15 +181,13 @@
  * @param onClick Associated [Clickable] for click events. When the button is clicked it will fire
  *   the associated action.
  * @param modifier Modifiers to set to this element. It's highly recommended to set a content
- *   description using [contentDescription].
- * @param shape Defines the button's shape, in other words the corner radius for this button.
- * @param backgroundColor The color to be used as a background of this button. If the background
- *   image is also specified, the image will be laid out on top of this color. In case of the fully
- *   opaque background image, then this background color will not be shown.
- * @param background The background object to be used behind the content in the button. It is
+ *   description using [contentDescription]. If [LayoutModifier.background] modifier is used and the
+ *   the background image is also specified, the image will be laid out on top of this color. In
+ *   case of the fully opaque background image, then the background color will not be shown.
+ * @param backgroundContent The background object to be used behind the content in the button. It is
  *   recommended to use the default styling that is automatically provided by only calling
- *   [backgroundImage] with the content. It can be combined with the specified [backgroundColor]
- *   behind it.
+ *   [backgroundImage] with the content. It can be combined with the specified
+ *   [LayoutModifier.background] behind it.
  * @param width The width of this button. It's highly recommended to set this to [expand] or
  *   [weight]
  * @param height The height of this button. It's highly recommended to set this to [expand] or
@@ -215,19 +210,15 @@
     height: ContainerDimension =
         if (content == null) IMAGE_BUTTON_DEFAULT_SIZE_DP.toDp()
         else wrapWithMinTapTargetDimension(),
-    shape: Corner = shapes.full,
-    backgroundColor: LayoutColor? = null,
-    background: (MaterialScope.() -> LayoutElement)? = null,
-    contentPadding: Padding = Padding.Builder().setAll(DEFAULT_CONTENT_PADDING_DP.toDp()).build()
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
+    contentPadding: Padding = DEFAULT_CONTENT_PADDING
 ): LayoutElement =
     componentContainer(
         onClick = onClick,
-        modifier = modifier,
+        modifier = LayoutModifier.clip(shapes.full) then modifier,
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = backgroundColor,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding,
         metadataTag = METADATA_TAG_BUTTON,
         content = content
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
index eff0392..f105a4b 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
@@ -19,10 +19,10 @@
 import android.graphics.Color
 import androidx.annotation.Dimension
 import androidx.annotation.Dimension.Companion.DP
-import androidx.wear.protolayout.ColorBuilders.argb
 import androidx.wear.protolayout.ModifiersBuilders.Padding
-import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING_DP
+import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING
 import androidx.wear.protolayout.material3.Typography.TypographyToken
+import androidx.wear.protolayout.modifiers.padding
 import androidx.wear.protolayout.types.LayoutColor
 import androidx.wear.protolayout.types.argb
 
@@ -81,7 +81,7 @@
         )
 
     internal const val METADATA_TAG_BUTTON: String = "BTN"
-    @Dimension(DP) internal const val DEFAULT_CONTENT_PADDING_DP: Int = 8
+    internal val DEFAULT_CONTENT_PADDING = padding(8f)
     @Dimension(DP) internal const val IMAGE_BUTTON_DEFAULT_SIZE_DP = 52
 }
 
@@ -89,7 +89,7 @@
 public class IconButtonStyle
 internal constructor(
     @Dimension(unit = DP) internal val iconSize: Int,
-    internal val innerPadding: Padding = DEFAULT_CONTENT_PADDING_DP.toPadding()
+    internal val innerPadding: Padding = DEFAULT_CONTENT_PADDING
 ) {
     public companion object {
         /**
@@ -110,7 +110,7 @@
 public class TextButtonStyle
 internal constructor(
     @TypographyToken internal val labelTypography: Int,
-    internal val innerPadding: Padding = DEFAULT_CONTENT_PADDING_DP.toPadding()
+    internal val innerPadding: Padding = DEFAULT_CONTENT_PADDING
 ) {
     public companion object {
         /**
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
index 318cc46..8a2d807 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
@@ -41,8 +41,10 @@
 import androidx.wear.protolayout.material3.TitleCardDefaults.buildContentForTitleCard
 import androidx.wear.protolayout.material3.TitleCardStyle.Companion.defaultTitleCardStyle
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.background
+import androidx.wear.protolayout.modifiers.clip
 import androidx.wear.protolayout.modifiers.contentDescription
-import androidx.wear.protolayout.types.LayoutColor
+import androidx.wear.protolayout.modifiers.padding
 
 /**
  * Opinionated ProtoLayout Material3 title card that offers 1 to 3 slots, usually text based.
@@ -69,7 +71,7 @@
  *   [CardDefaults.filledTonalCardColors] for low/medium emphasis card,
  *   [CardDefaults.imageBackgroundCardColors] for card with image as a background or custom built
  *   [CardColors].
- * @param background The background object to be used behind the content in the card. It is
+ * @param backgroundContent The background object to be used behind the content in the card. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified [colors]'s background
  *   color behind it.
@@ -95,7 +97,7 @@
     shape: Corner =
         if (deviceConfiguration.screenWidthDp.isBreakpoint()) shapes.extraLarge else shapes.large,
     colors: CardColors = filledCardColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: TitleCardStyle = defaultTitleCardStyle(),
     contentPadding: Padding = style.innerPadding,
     @HorizontalAlignment
@@ -103,12 +105,10 @@
 ): LayoutElement =
     card(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(colors.background).clip(shape),
         width = expand(),
         height = height,
-        shape = shape,
-        backgroundColor = colors.background,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding
     ) {
         buildContentForTitleCard(
@@ -193,7 +193,7 @@
  *   [CardDefaults.filledTonalCardColors] for low/medium emphasis card,
  *   [CardDefaults.imageBackgroundCardColors] for card with image as a background or custom built
  *   [CardColors].
- * @param background The background object to be used behind the content in the card. It is
+ * @param backgroundContent The background object to be used behind the content in the card. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified [colors]'s background
  *   color behind it.
@@ -218,18 +218,16 @@
     shape: Corner =
         if (deviceConfiguration.screenWidthDp.isBreakpoint()) shapes.extraLarge else shapes.large,
     colors: CardColors = filledCardColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: AppCardStyle = defaultAppCardStyle(),
     contentPadding: Padding = style.innerPadding,
 ): LayoutElement =
     card(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(colors.background).clip(shape),
         width = expand(),
         height = height,
-        shape = shape,
-        backgroundColor = colors.background,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding
     ) {
         buildContentForAppCard(
@@ -321,7 +319,7 @@
  *   [CardDefaults.filledTonalCardColors] for low/medium emphasis card,
  *   [CardDefaults.imageBackgroundCardColors] for card with image as a background or custom built
  *   [CardColors].
- * @param background The background object to be used behind the content in the card. It is
+ * @param backgroundContent The background object to be used behind the content in the card. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified [colors]'s background
  *   color behind it.
@@ -349,19 +347,17 @@
     height: ContainerDimension = wrapWithMinTapTargetDimension(),
     shape: Corner = shapes.large,
     colors: CardColors = filledCardColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: DataCardStyle =
         if (secondaryText == null) defaultCompactDataCardStyle() else defaultDataCardStyle(),
     contentPadding: Padding = style.innerPadding,
 ): LayoutElement =
     card(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(colors.background).clip(shape),
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = colors.background,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding
     ) {
         buildContentForDataCard(
@@ -435,7 +431,7 @@
  *   [CardDefaults.filledTonalCardColors] for low/medium emphasis card,
  *   [CardDefaults.imageBackgroundCardColors] for card with image as a background or custom built
  *   [CardColors].
- * @param background The background object to be used behind the content in the card. It is
+ * @param backgroundContent The background object to be used behind the content in the card. It is
  *   recommended to use the default styling that is automatically provided by only calling
  *   [backgroundImage] with the content. It can be combined with the specified [colors]'s background
  *   color behind it.
@@ -465,7 +461,7 @@
     height: ContainerDimension = wrapWithMinTapTargetDimension(),
     shape: Corner = shapes.large,
     colors: CardColors = filledCardColors(),
-    background: (MaterialScope.() -> LayoutElement)? = null,
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
     style: DataCardStyle =
         if (secondaryIcon == null) defaultCompactDataCardStyle() else defaultDataCardStyle(),
     titleContentPlacement: TitleContentPlacementInDataCard = TitleContentPlacementInDataCard.Bottom,
@@ -473,12 +469,10 @@
 ): LayoutElement =
     card(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(colors.background).clip(shape),
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = colors.background,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding
     ) {
         buildContentForDataCard(
@@ -570,11 +564,9 @@
 ): LayoutElement =
     card(
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.background(colors.background).clip(shape),
         width = expand(),
         height = height,
-        shape = shape,
-        backgroundColor = colors.background,
         contentPadding = contentPadding
     ) {
         buildContentForGraphicDataCard(
@@ -628,15 +620,13 @@
  * @param onClick Associated [Clickable] for click events. When the card is clicked it will fire the
  *   associated action.
  * @param modifier Modifiers to set to this element. It's highly recommended to set a content
- *   description using [contentDescription].
- * @param shape Defines the card's shape, in other words the corner radius for this card.
- * @param backgroundColor The color to be used as a background of this card. If the background image
- *   is also specified, the image will be laid out on top of this color. In case of the fully opaque
- *   background image, then this background color will not be shown.
- * @param background The background object to be used behind the content in the card. It is
+ *   description using [contentDescription]. If [LayoutModifier.background] modifier is used and the
+ *   the background image is also specified, the image will be laid out on top of this color. In
+ *   case of the fully opaque background image, then the background color will not be shown.
+ * @param backgroundContent The background object to be used behind the content in the card. It is
  *   recommended to use the default styling that is automatically provided by only calling
- *   [backgroundImage] with the content. It can be combined with the specified [backgroundColor]
- *   behind it.
+ *   [backgroundImage] with the content. It can be combined with the specified
+ *   [LayoutModifier.background] behind it.
  * @param width The width of this card. It's highly recommended to set this to [expand] or [weight]
  * @param height The height of this card. It's highly recommended to set this to [expand] or
  *   [weight]
@@ -652,20 +642,16 @@
     modifier: LayoutModifier = LayoutModifier,
     width: ContainerDimension = wrapWithMinTapTargetDimension(),
     height: ContainerDimension = wrapWithMinTapTargetDimension(),
-    shape: Corner = shapes.large,
-    backgroundColor: LayoutColor? = null,
-    background: (MaterialScope.() -> LayoutElement)? = null,
-    contentPadding: Padding = Padding.Builder().setAll(DEFAULT_CONTENT_PADDING.toDp()).build(),
+    backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
+    contentPadding: Padding = padding(DEFAULT_CONTENT_PADDING),
     content: (MaterialScope.() -> LayoutElement)
 ): LayoutElement =
     componentContainer(
         onClick = onClick,
-        modifier = modifier,
+        modifier = LayoutModifier.clip(shapes.large) then modifier,
         width = width,
         height = height,
-        shape = shape,
-        backgroundColor = backgroundColor,
-        background = background,
+        backgroundContent = backgroundContent,
         contentPadding = contentPadding,
         metadataTag = METADATA_TAG,
         content = content
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CardDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CardDefaults.kt
index 77bdda2..ad91cc6 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CardDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CardDefaults.kt
@@ -94,5 +94,5 @@
         )
 
     internal const val METADATA_TAG: String = "CR"
-    internal const val DEFAULT_CONTENT_PADDING: Int = 4
+    internal const val DEFAULT_CONTENT_PADDING = 4f
 }
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/DataCardDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/DataCardDefaults.kt
index f6c083b..d6552a3 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/DataCardDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/DataCardDefaults.kt
@@ -24,6 +24,7 @@
 import androidx.wear.protolayout.material3.TitleContentPlacementInDataCard.Companion.Bottom
 import androidx.wear.protolayout.material3.TitleContentPlacementInDataCard.Companion.Top
 import androidx.wear.protolayout.material3.Typography.TypographyToken
+import androidx.wear.protolayout.modifiers.padding
 
 internal object DataCardDefaults {
     /**
@@ -127,13 +128,13 @@
 
         @Dimension(unit = DP) private const val ICON_SIZE_LARGE_DP: Int = 32
 
-        @Dimension(unit = DP) private const val PADDING_SMALL_DP: Int = 8
+        @Dimension(unit = DP) private const val PADDING_SMALL_DP = 8f
 
-        @Dimension(unit = DP) private const val PADDING_DEFAULT_DP: Int = 10
+        @Dimension(unit = DP) private const val PADDING_DEFAULT_DP = 10f
 
-        @Dimension(unit = DP) private const val PADDING_LARGE_DP: Int = 14
+        @Dimension(unit = DP) private const val PADDING_LARGE_DP = 14f
 
-        @Dimension(unit = DP) private const val PADDING_EXTRA_LARGE_DP: Int = 16
+        @Dimension(unit = DP) private const val PADDING_EXTRA_LARGE_DP = 16f
 
         /**
          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
@@ -141,7 +142,7 @@
          */
         public fun smallDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding = PADDING_SMALL_DP.toPadding(),
+                innerPadding = padding(PADDING_SMALL_DP),
                 titleToContentSpaceDp = SMALL_SPACE_DP,
                 titleTypography = Typography.LABEL_MEDIUM,
                 contentTypography = Typography.BODY_SMALL,
@@ -155,7 +156,7 @@
          */
         public fun defaultDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding = PADDING_DEFAULT_DP.toPadding(),
+                innerPadding = padding(PADDING_DEFAULT_DP),
                 titleToContentSpaceDp = SMALL_SPACE_DP,
                 titleTypography = Typography.LABEL_LARGE,
                 contentTypography = Typography.BODY_SMALL,
@@ -169,7 +170,7 @@
          */
         public fun largeDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding = PADDING_DEFAULT_DP.toPadding(),
+                innerPadding = padding(PADDING_DEFAULT_DP),
                 titleToContentSpaceDp = EMPTY_SPACE_DP,
                 titleTypography = Typography.DISPLAY_SMALL,
                 contentTypography = Typography.BODY_SMALL,
@@ -184,12 +185,7 @@
         public fun extraLargeDataCardStyle(): DataCardStyle =
             DataCardStyle(
                 innerPadding =
-                    Padding.Builder()
-                        .setStart(PADDING_DEFAULT_DP.toDp())
-                        .setEnd(PADDING_DEFAULT_DP.toDp())
-                        .setTop(PADDING_EXTRA_LARGE_DP.toDp())
-                        .setBottom(PADDING_EXTRA_LARGE_DP.toDp())
-                        .build(),
+                    padding(horizontal = PADDING_DEFAULT_DP, vertical = PADDING_EXTRA_LARGE_DP),
                 titleToContentSpaceDp = EMPTY_SPACE_DP,
                 titleTypography = Typography.DISPLAY_MEDIUM,
                 contentTypography = Typography.BODY_SMALL,
@@ -204,13 +200,7 @@
          */
         public fun smallCompactDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding =
-                    Padding.Builder()
-                        .setTop(PADDING_SMALL_DP.toDp())
-                        .setBottom(PADDING_SMALL_DP.toDp())
-                        .setStart(PADDING_LARGE_DP.toDp())
-                        .setEnd(PADDING_LARGE_DP.toDp())
-                        .build(),
+                innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
                 titleToContentSpaceDp = DEFAULT_SPACE_DP,
                 titleTypography = Typography.NUMERAL_MEDIUM,
                 contentTypography = Typography.LABEL_MEDIUM,
@@ -225,13 +215,7 @@
          */
         public fun defaultCompactDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding =
-                    Padding.Builder()
-                        .setTop(PADDING_SMALL_DP.toDp())
-                        .setBottom(PADDING_SMALL_DP.toDp())
-                        .setStart(PADDING_LARGE_DP.toDp())
-                        .setEnd(PADDING_LARGE_DP.toDp())
-                        .build(),
+                innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
                 titleToContentSpaceDp = EMPTY_SPACE_DP,
                 titleTypography = Typography.NUMERAL_LARGE,
                 contentTypography = Typography.LABEL_LARGE,
@@ -246,13 +230,7 @@
          */
         public fun largeCompactDataCardStyle(): DataCardStyle =
             DataCardStyle(
-                innerPadding =
-                    Padding.Builder()
-                        .setTop(PADDING_SMALL_DP.toDp())
-                        .setBottom(PADDING_SMALL_DP.toDp())
-                        .setStart(PADDING_LARGE_DP.toDp())
-                        .setEnd(PADDING_LARGE_DP.toDp())
-                        .build(),
+                innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
                 titleToContentSpaceDp = EMPTY_SPACE_DP,
                 titleTypography = Typography.NUMERAL_EXTRA_LARGE,
                 contentTypography = Typography.LABEL_LARGE,
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/EdgeButton.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/EdgeButton.kt
index 227f92c..cf093bc 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/EdgeButton.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/EdgeButton.kt
@@ -16,17 +16,16 @@
 
 package androidx.wear.protolayout.material3
 
-import androidx.wear.protolayout.DimensionBuilders.DpProp
+import android.R.attr.clickable
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
 import androidx.wear.protolayout.DimensionBuilders.dp
 import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.LayoutElementBuilders.Box
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
 import androidx.wear.protolayout.LayoutElementBuilders.VERTICAL_ALIGN_CENTER
 import androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignment
-import androidx.wear.protolayout.ModifiersBuilders.Background
 import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.ModifiersBuilders.Corner
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
 import androidx.wear.protolayout.ModifiersBuilders.Padding
 import androidx.wear.protolayout.ModifiersBuilders.SEMANTICS_ROLE_BUTTON
 import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
@@ -42,9 +41,16 @@
 import androidx.wear.protolayout.material3.EdgeButtonStyle.Companion.DEFAULT
 import androidx.wear.protolayout.material3.EdgeButtonStyle.Companion.TOP_ALIGN
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.background
+import androidx.wear.protolayout.modifiers.clickable
+import androidx.wear.protolayout.modifiers.clip
+import androidx.wear.protolayout.modifiers.clipBottomLeft
+import androidx.wear.protolayout.modifiers.clipBottomRight
 import androidx.wear.protolayout.modifiers.contentDescription
+import androidx.wear.protolayout.modifiers.padding
 import androidx.wear.protolayout.modifiers.semanticsRole
-import androidx.wear.protolayout.modifiers.toProtoLayoutModifiersBuilder
+import androidx.wear.protolayout.modifiers.tag
+import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
 
 /**
  * ProtoLayout Material3 component edge button that offers a single slot to take an icon or similar
@@ -167,26 +173,18 @@
         else HORIZONTAL_MARGIN_PERCENT_SMALL
     val edgeButtonWidth: Float =
         (100f - 2f * horizontalMarginPercent) * deviceConfiguration.screenWidthDp / 100f
-    val bottomCornerRadiusX = dp(edgeButtonWidth / 2f)
-    val bottomCornerRadiusY = dp(EDGE_BUTTON_HEIGHT_DP - TOP_CORNER_RADIUS.value)
+    val bottomCornerRadiusX = edgeButtonWidth / 2f
+    val bottomCornerRadiusY = EDGE_BUTTON_HEIGHT_DP - TOP_CORNER_RADIUS
 
-    val defaultModifier = LayoutModifier.semanticsRole(SEMANTICS_ROLE_BUTTON) then modifier
-    val modifiers =
-        defaultModifier
-            .toProtoLayoutModifiersBuilder()
-            .setClickable(onClick)
-            .setBackground(
-                Background.Builder()
-                    .setColor(colors.container.prop)
-                    .setCorner(
-                        Corner.Builder()
-                            .setRadius(TOP_CORNER_RADIUS)
-                            .setBottomLeftRadius(bottomCornerRadiusX, bottomCornerRadiusY)
-                            .setBottomRightRadius(bottomCornerRadiusX, bottomCornerRadiusY)
-                            .build()
-                    )
-                    .build()
-            )
+    var mod =
+        (LayoutModifier.semanticsRole(SEMANTICS_ROLE_BUTTON) then modifier)
+            .clickable(onClick)
+            .background(colors.container)
+            .clip(TOP_CORNER_RADIUS)
+            .clipBottomLeft(bottomCornerRadiusX, bottomCornerRadiusY)
+            .clipBottomRight(bottomCornerRadiusX, bottomCornerRadiusY)
+
+    style.padding?.let { mod = mod.padding(it) }
 
     val button = Box.Builder().setHeight(EDGE_BUTTON_HEIGHT_DP.toDp()).setWidth(dp(edgeButtonWidth))
     button
@@ -194,15 +192,13 @@
         .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
         .addContent(content())
 
-    style.padding?.let { modifiers.setPadding(it) }
-
     return Box.Builder()
         .setHeight((EDGE_BUTTON_HEIGHT_DP + BOTTOM_MARGIN_DP).toDp())
         .setWidth(containerWidth)
         .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_TOP)
         .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
-        .addContent(button.setModifiers(modifiers.build()).build())
-        .setModifiers(Modifiers.Builder().setMetadata(METADATA_TAG.toElementMetadata()).build())
+        .addContent(button.setModifiers(mod.toProtoLayoutModifiers()).build())
+        .setModifiers(LayoutModifier.tag(METADATA_TAG).toProtoLayoutModifiers())
         .build()
 }
 
@@ -224,11 +220,11 @@
             EdgeButtonStyle(
                 verticalAlignment = LayoutElementBuilders.VERTICAL_ALIGN_TOP,
                 padding =
-                    Padding.Builder()
-                        .setTop(TEXT_TOP_PADDING_DP.toDp())
-                        .setStart(TEXT_SIDE_PADDING_DP.toDp())
-                        .setEnd(TEXT_SIDE_PADDING_DP.toDp())
-                        .build()
+                    padding(
+                        start = TEXT_SIDE_PADDING_DP,
+                        top = TEXT_TOP_PADDING_DP,
+                        end = TEXT_SIDE_PADDING_DP
+                    )
             )
 
         /**
@@ -242,7 +238,7 @@
 }
 
 internal object EdgeButtonDefaults {
-    @JvmField internal val TOP_CORNER_RADIUS: DpProp = dp(17f)
+    @Dimension(DP) internal const val TOP_CORNER_RADIUS: Float = 17f
     /** The horizontal margin used for width of the EdgeButton, below the 225dp breakpoint. */
     internal const val HORIZONTAL_MARGIN_PERCENT_SMALL: Float = 24f
     /** The horizontal margin used for width of the EdgeButton, above the 225dp breakpoint. */
@@ -251,8 +247,8 @@
     internal const val EDGE_BUTTON_HEIGHT_DP: Int = 46
     internal const val METADATA_TAG: String = "EB"
     internal const val ICON_SIZE_DP = 24
-    internal const val TEXT_TOP_PADDING_DP = 12
-    internal const val TEXT_SIDE_PADDING_DP = 8
+    internal const val TEXT_TOP_PADDING_DP = 12f
+    internal const val TEXT_SIDE_PADDING_DP = 8f
 }
 
 internal fun LayoutElement.isSlotEdgeButton(): Boolean =
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/GraphicDataCardDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/GraphicDataCardDefaults.kt
index dc256d7..c931cb6 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/GraphicDataCardDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/GraphicDataCardDefaults.kt
@@ -31,6 +31,7 @@
 import androidx.wear.protolayout.LayoutElementBuilders.Row
 import androidx.wear.protolayout.ModifiersBuilders.Padding
 import androidx.wear.protolayout.material3.Typography.TypographyToken
+import androidx.wear.protolayout.modifiers.padding
 
 internal object GraphicDataCardDefaults {
     @FloatRange(from = 0.0, to = 100.0) private const val GRAPHIC_SPACE_PERCENTAGE: Float = 40f
@@ -135,7 +136,7 @@
         /** The default smaller spacer width or height that should be between different elements. */
         @Dimension(unit = DP) private const val SMALL_SPACE_DP: Int = 2
 
-        private const val DEFAULT_VERTICAL_PADDING_DP = 8
+        private const val DEFAULT_VERTICAL_PADDING_DP = 8f
 
         /**
          * Default style variation for the [graphicDataCard] where all opinionated inner content is
@@ -144,10 +145,10 @@
         public fun defaultGraphicDataCardStyle(): GraphicDataCardStyle =
             GraphicDataCardStyle(
                 innerPadding =
-                    Padding.Builder()
-                        .setTop(DEFAULT_VERTICAL_PADDING_DP.toDp())
-                        .setBottom(DEFAULT_VERTICAL_PADDING_DP.toDp())
-                        .build(),
+                    padding(
+                        top = DEFAULT_VERTICAL_PADDING_DP,
+                        bottom = DEFAULT_VERTICAL_PADDING_DP
+                    ),
                 titleToContentSpaceDp = SMALL_SPACE_DP,
                 titleTypography = Typography.DISPLAY_SMALL,
                 contentTypography = Typography.LABEL_SMALL,
@@ -162,10 +163,10 @@
         public fun largeGraphicDataCardStyle(): GraphicDataCardStyle =
             GraphicDataCardStyle(
                 innerPadding =
-                    Padding.Builder()
-                        .setTop(DEFAULT_VERTICAL_PADDING_DP.toDp())
-                        .setBottom(DEFAULT_VERTICAL_PADDING_DP.toDp())
-                        .build(),
+                    padding(
+                        top = DEFAULT_VERTICAL_PADDING_DP,
+                        bottom = DEFAULT_VERTICAL_PADDING_DP
+                    ),
                 titleToContentSpaceDp = 0,
                 titleTypography = Typography.DISPLAY_MEDIUM,
                 contentTypography = Typography.LABEL_MEDIUM,
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
index 09b8979..ed51c87 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
@@ -40,17 +40,17 @@
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
 import androidx.wear.protolayout.LayoutElementBuilders.Spacer
 import androidx.wear.protolayout.LayoutElementBuilders.TextAlignment
-import androidx.wear.protolayout.ModifiersBuilders.Background
 import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.ModifiersBuilders.Corner
 import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
 import androidx.wear.protolayout.ModifiersBuilders.Padding
 import androidx.wear.protolayout.ModifiersBuilders.SEMANTICS_ROLE_BUTTON
 import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverterFactory
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.clickable
+import androidx.wear.protolayout.modifiers.padding
 import androidx.wear.protolayout.modifiers.semanticsRole
-import androidx.wear.protolayout.modifiers.toProtoLayoutModifiersBuilder
+import androidx.wear.protolayout.modifiers.tag
+import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
 import androidx.wear.protolayout.types.LayoutColor
 import androidx.wear.protolayout.types.argb
 import java.nio.charset.StandardCharsets
@@ -91,8 +91,6 @@
 
 internal fun Int.toDp() = dp(this.toFloat())
 
-internal fun String.toElementMetadata() = ElementMetadata.Builder().setTagData(toTagBytes()).build()
-
 /** Builds a horizontal Spacer, with width set to expand and height set to the given value. */
 internal fun horizontalSpacer(@Dimension(unit = DP) heightDp: Int): Spacer =
     Spacer.Builder().setWidth(expand()).setHeight(heightDp.toDp()).build()
@@ -112,15 +110,6 @@
 internal fun wrapWithMinTapTargetDimension(): WrappedDimensionProp =
     WrappedDimensionProp.Builder().setMinimumSize(MINIMUM_TAP_TARGET_SIZE).build()
 
-/** Returns the [Modifiers] object containing this padding and nothing else. */
-internal fun Padding.toModifiers(): Modifiers = Modifiers.Builder().setPadding(this).build()
-
-/** Returns the [Background] object containing this color and nothing else. */
-internal fun LayoutColor.toBackground(): Background = Background.Builder().setColor(prop).build()
-
-/** Returns the [Background] object containing this corner and nothing else. */
-internal fun Corner.toBackground(): Background = Background.Builder().setCorner(this).build()
-
 /**
  * Changes the opacity/transparency of the given color.
  *
@@ -156,8 +145,6 @@
  */
 internal fun Int.isBreakpoint() = this >= SCREEN_SIZE_BREAKPOINT_DP
 
-internal fun Int.toPadding(): Padding = Padding.Builder().setAll(this.toDp()).build()
-
 /**
  * Builds [Box] that represents a clickable container with the given [content] inside, and
  * [SEMANTICS_ROLE_BUTTON], that can be used to create container or more opinionated card or button
@@ -168,37 +155,29 @@
     modifier: LayoutModifier,
     width: ContainerDimension,
     height: ContainerDimension,
-    shape: Corner,
-    backgroundColor: LayoutColor?,
-    background: (MaterialScope.() -> LayoutElement)?,
+    backgroundContent: (MaterialScope.() -> LayoutElement)?,
     contentPadding: Padding,
     metadataTag: String,
     content: (MaterialScope.() -> LayoutElement)?
 ): LayoutElement {
-    val backgroundBuilder = Background.Builder().setCorner(shape)
-    backgroundColor?.let { backgroundBuilder.setColor(it.prop) }
 
-    val defaultModifier = LayoutModifier.semanticsRole(SEMANTICS_ROLE_BUTTON) then modifier
-    val modifiers =
-        defaultModifier
-            .toProtoLayoutModifiersBuilder()
-            .setClickable(onClick)
-            .setMetadata(metadataTag.toElementMetadata())
-            .setBackground(backgroundBuilder.build())
+    val mod =
+        LayoutModifier.semanticsRole(SEMANTICS_ROLE_BUTTON) then
+            modifier.clickable(onClick).tag(metadataTag)
 
     val container =
         Box.Builder().setHeight(height).setWidth(width).apply {
             content?.let { addContent(content()) }
         }
 
-    if (background == null) {
-        modifiers.setPadding(contentPadding)
-        container.setModifiers(modifiers.build())
+    if (backgroundContent == null) {
+        container.setModifiers(mod.padding(contentPadding).toProtoLayoutModifiers())
         return container.build()
     }
 
+    val protoLayoutModifiers = mod.toProtoLayoutModifiers()
     return Box.Builder()
-        .setModifiers(modifiers.build())
+        .setModifiers(protoLayoutModifiers)
         .addContent(
             withStyle(
                     defaultBackgroundImageStyle =
@@ -208,18 +187,18 @@
                             overlayColor = colorScheme.primary.withOpacity(0.6f),
                             overlayWidth = width,
                             overlayHeight = height,
-                            shape = shape,
+                            shape = protoLayoutModifiers.background?.corner ?: shapes.large,
                             contentScaleMode = LayoutElementBuilders.CONTENT_SCALE_MODE_FILL_BOUNDS
                         )
                 )
-                .background()
+                .backgroundContent()
         )
         .setWidth(width)
         .setHeight(height)
         .addContent(
             container
                 // Padding in this case is needed on the inner content, not the whole card.
-                .setModifiers(contentPadding.toModifiers())
+                .setModifiers(LayoutModifier.padding(contentPadding).toProtoLayoutModifiers())
                 .build()
         )
         .build()
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Image.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Image.kt
index ab3a3aa..b14da86 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Image.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Image.kt
@@ -22,8 +22,10 @@
 import androidx.wear.protolayout.LayoutElementBuilders.ContentScaleMode
 import androidx.wear.protolayout.LayoutElementBuilders.Image
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
-import androidx.wear.protolayout.ModifiersBuilders.Corner
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
+import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.background
+import androidx.wear.protolayout.modifiers.clip
+import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
 import androidx.wear.protolayout.types.LayoutColor
 
 /**
@@ -34,6 +36,7 @@
  *
  * @param protoLayoutResourceId The protolayout resource id of the image. Node that, this is not an
  *   Android resource id.
+ * @param modifier Modifiers to set to this element.
  * @param width The width of an image. Usually, this matches the width of the parent component this
  *   is used in.
  * @param height The height of an image. Usually, this matches the height of the parent component
@@ -42,18 +45,17 @@
  *   It's recommended to use [ColorScheme.background] color with 60% opacity.
  * @param overlayWidth The width of the overlay on top of the image background
  * @param overlayHeight The height of the overlay on top of the image background
- * @param shape The shape of the corners for the image
  * @param contentScaleMode The content scale mode for the image to define how image will adapt to
  *   the given size
  */
 public fun MaterialScope.backgroundImage(
     protoLayoutResourceId: String,
+    modifier: LayoutModifier = LayoutModifier,
     width: ImageDimension = defaultBackgroundImageStyle.width,
     height: ImageDimension = defaultBackgroundImageStyle.height,
     overlayColor: LayoutColor = defaultBackgroundImageStyle.overlayColor,
     overlayWidth: ContainerDimension = defaultBackgroundImageStyle.overlayWidth,
     overlayHeight: ContainerDimension = defaultBackgroundImageStyle.overlayHeight,
-    shape: Corner = defaultBackgroundImageStyle.shape,
     @ContentScaleMode contentScaleMode: Int = defaultBackgroundImageStyle.contentScaleMode,
 ): LayoutElement =
     Box.Builder()
@@ -64,7 +66,10 @@
             Image.Builder()
                 .setWidth(width)
                 .setHeight(height)
-                .setModifiers(Modifiers.Builder().setBackground(shape.toBackground()).build())
+                .setModifiers(
+                    (LayoutModifier.clip(defaultBackgroundImageStyle.shape) then modifier)
+                        .toProtoLayoutModifiers()
+                )
                 .setResourceId(protoLayoutResourceId)
                 .setContentScaleMode(contentScaleMode)
                 .build()
@@ -74,9 +79,7 @@
             Box.Builder()
                 .setWidth(overlayWidth)
                 .setHeight(overlayHeight)
-                .setModifiers(
-                    Modifiers.Builder().setBackground(overlayColor.toBackground()).build()
-                )
+                .setModifiers(LayoutModifier.background(overlayColor).toProtoLayoutModifiers())
                 .build()
         )
         .build()
@@ -90,9 +93,9 @@
  *
  * @param protoLayoutResourceId The protolayout resource id of the image. Node that, this is not an
  *   Android resource id.
+ * @param modifier Modifiers to set to this element.
  * @param width The width of an image. Usually, a small image that fit into the component's slot.
  * @param height The height of an image. Usually, a small image that fit into the component's slot.
- * @param shape The shape of the corners for the image. Usually it's circular image.
  * @param contentScaleMode The content scale mode for the image to define how image will adapt to
  *   the given size
  */
@@ -100,13 +103,16 @@
     protoLayoutResourceId: String,
     width: ImageDimension = defaultAvatarImageStyle.width,
     height: ImageDimension = defaultAvatarImageStyle.height,
-    shape: Corner = defaultAvatarImageStyle.shape,
+    modifier: LayoutModifier = LayoutModifier,
     @ContentScaleMode contentScaleMode: Int = defaultAvatarImageStyle.contentScaleMode,
 ): LayoutElement =
     Image.Builder()
         .setWidth(width)
         .setHeight(height)
-        .setModifiers(Modifiers.Builder().setBackground(shape.toBackground()).build())
+        .setModifiers(
+            (LayoutModifier.clip(defaultAvatarImageStyle.shape) then modifier)
+                .toProtoLayoutModifiers()
+        )
         .setResourceId(protoLayoutResourceId)
         .setContentScaleMode(contentScaleMode)
         .build()
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/PrimaryLayout.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/PrimaryLayout.kt
index eb361c5..0bd6491 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/PrimaryLayout.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/PrimaryLayout.kt
@@ -99,7 +99,7 @@
  *   it an edge button, the given label will be ignored.
  * @param onClick The clickable action for whole layout. If any area (outside of other added
  *   tappable components) is clicked, it will fire the associated action.
- * @sample androidx.wear.protolayout.material3.samples.topLeveLayout
+ * @sample androidx.wear.protolayout.material3.samples.topLevelLayout
  */
 // TODO: b/356568440 - Add sample above and put it in a proper samples file and link with @sample
 // TODO: b/346958146 - Link visuals once they are available.
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
index ba6ab99..e56e836 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
@@ -23,6 +23,8 @@
 import androidx.wear.protolayout.DeviceParametersBuilders
 import androidx.wear.protolayout.DimensionBuilders.expand
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.backgroundColor
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.testing.LayoutElementAssertionsProvider
 import androidx.wear.protolayout.testing.hasClickable
@@ -96,7 +98,7 @@
     fun containerButton_hasClickable() {
         LayoutElementAssertionsProvider(DEFAULT_CONTAINER_BUTTON_WITH_TEXT)
             .onRoot()
-            .assert(hasClickable(CLICKABLE))
+            .assert(hasClickable(id = CLICKABLE.id))
             .assert(hasTag(ButtonDefaults.METADATA_TAG_BUTTON))
     }
 
@@ -126,7 +128,7 @@
                 button(
                     onClick = CLICKABLE,
                     modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
-                    background = { backgroundImage(IMAGE_ID) }
+                    backgroundContent = { backgroundImage(IMAGE_ID) }
                 )
             }
 
@@ -143,8 +145,9 @@
             materialScope(CONTEXT, DEVICE_CONFIGURATION) {
                 button(
                     onClick = CLICKABLE,
-                    modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
-                    backgroundColor = color.argb,
+                    modifier =
+                        LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
+                            .backgroundColor(color.argb),
                     content = { text(TEXT.layoutString) }
                 )
             }
@@ -187,7 +190,7 @@
                 .setScreenHeightDp(192)
                 .build()
 
-        private val CLICKABLE = clickable("id")
+        private val CLICKABLE = clickable(id = "id")
 
         private const val CONTENT_DESCRIPTION = "This is a button"
 
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
index 01c2a3c..d2d2a81 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
@@ -23,6 +23,8 @@
 import androidx.wear.protolayout.DeviceParametersBuilders
 import androidx.wear.protolayout.DimensionBuilders.expand
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.background
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.testing.LayoutElementAssertionsProvider
 import androidx.wear.protolayout.testing.hasClickable
@@ -99,7 +101,7 @@
     fun containerCard_hasClickable() {
         LayoutElementAssertionsProvider(DEFAULT_CONTAINER_CARD_WITH_TEXT)
             .onRoot()
-            .assert(hasClickable(CLICKABLE))
+            .assert(hasClickable(id = CLICKABLE.id))
             .assert(hasTag(CardDefaults.METADATA_TAG))
     }
 
@@ -222,7 +224,7 @@
                 card(
                     onClick = CLICKABLE,
                     modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
-                    background = { backgroundImage(IMAGE_ID) }
+                    backgroundContent = { backgroundImage(IMAGE_ID) }
                 ) {
                     text(TEXT.layoutString)
                 }
@@ -239,8 +241,10 @@
             materialScope(CONTEXT, DEVICE_CONFIGURATION) {
                 card(
                     onClick = CLICKABLE,
-                    modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
-                    backgroundColor = color.argb
+                    modifier =
+                        LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
+                            .background(color.argb)
+                            .clickable(id = "id")
                 ) {
                     text(TEXT.layoutString)
                 }
@@ -458,7 +462,7 @@
                 .setScreenHeightDp(192)
                 .build()
 
-        private val CLICKABLE = clickable("id")
+        private val CLICKABLE = clickable(id = "id")
 
         private const val CONTENT_DESCRIPTION = "This is a card"
 
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
index bea992cf..ec85a48 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
@@ -16,18 +16,19 @@
 
 package androidx.wear.protolayout.material3
 
+import android.content.ComponentName
 import android.content.Context
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.wear.protolayout.ActionBuilders.LaunchAction
+import androidx.wear.protolayout.ActionBuilders.launchAction
 import androidx.wear.protolayout.DeviceParametersBuilders
 import androidx.wear.protolayout.LayoutElementBuilders.Image
-import androidx.wear.protolayout.ModifiersBuilders.Clickable
 import androidx.wear.protolayout.expression.AppDataKey
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32
 import androidx.wear.protolayout.material3.EdgeButtonDefaults.BOTTOM_MARGIN_DP
 import androidx.wear.protolayout.material3.EdgeButtonDefaults.EDGE_BUTTON_HEIGHT_DP
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.testing.LayoutElementAssertionsProvider
 import androidx.wear.protolayout.testing.LayoutElementMatcher
@@ -173,10 +174,7 @@
                 .build()
 
         private val CLICKABLE =
-            Clickable.Builder()
-                .setOnClick(LaunchAction.Builder().build())
-                .setId("action_id")
-                .build()
+            clickable(action = launchAction(ComponentName("pkg", "cls")), id = "action_id")
 
         private const val CONTENT_DESCRIPTION = "it is an edge button"
 
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/Utils.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/Utils.kt
index b671a70..7d765cc 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/Utils.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/Utils.kt
@@ -19,8 +19,6 @@
 import android.content.Context
 import android.provider.Settings
 import androidx.test.core.app.ApplicationProvider
-import androidx.wear.protolayout.ActionBuilders.LaunchAction
-import androidx.wear.protolayout.ModifiersBuilders.Clickable
 
 // TODO: b/373336064 - Move this to protolayout-material3-testing
 internal fun enableDynamicTheme() {
@@ -30,6 +28,3 @@
         /* dynamic theming is enabled */ 1
     )
 }
-
-internal fun clickable(id: String) =
-    Clickable.Builder().setOnClick(LaunchAction.Builder().build()).setId(id).build()
diff --git a/wear/protolayout/protolayout-testing/api/current.txt b/wear/protolayout/protolayout-testing/api/current.txt
index 1503d9d1..544da65 100644
--- a/wear/protolayout/protolayout-testing/api/current.txt
+++ b/wear/protolayout/protolayout-testing/api/current.txt
@@ -1,10 +1,14 @@
 // Signature format: 4.0
 package androidx.wear.protolayout.testing {
 
-  public final class FiltersKt {
+  public final class Filters {
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher containsTag(String value);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasChild(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
-    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable clickable);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable();
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableWidth);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableHeight);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasColor(@ColorInt int argb);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasContentDescription(String value);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasContentDescription(kotlin.text.Regex pattern);
diff --git a/wear/protolayout/protolayout-testing/api/restricted_current.txt b/wear/protolayout/protolayout-testing/api/restricted_current.txt
index 1503d9d1..544da65 100644
--- a/wear/protolayout/protolayout-testing/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-testing/api/restricted_current.txt
@@ -1,10 +1,14 @@
 // Signature format: 4.0
 package androidx.wear.protolayout.testing {
 
-  public final class FiltersKt {
+  public final class Filters {
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher containsTag(String value);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasChild(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
-    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable clickable);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable();
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableWidth);
+    method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasClickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minClickableHeight);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasColor(@ColorInt int argb);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasContentDescription(String value);
     method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasContentDescription(kotlin.text.Regex pattern);
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
index 8a8a141..13ded25 100644
--- a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
@@ -13,11 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:JvmName("Filters")
 
 package androidx.wear.protolayout.testing
 
 import androidx.annotation.ColorInt
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
 import androidx.annotation.RestrictTo
+import androidx.wear.protolayout.ActionBuilders.Action
 import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
 import androidx.wear.protolayout.DimensionBuilders.DpProp
 import androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp
@@ -33,6 +37,7 @@
 import androidx.wear.protolayout.LayoutElementBuilders.Spannable
 import androidx.wear.protolayout.LayoutElementBuilders.Text
 import androidx.wear.protolayout.ModifiersBuilders.Clickable
+import androidx.wear.protolayout.modifiers.loadAction
 import androidx.wear.protolayout.proto.DimensionProto
 import androidx.wear.protolayout.types.LayoutString
 
@@ -44,9 +49,22 @@
  * Returns a [LayoutElementMatcher] which checks whether the element has the specific [Clickable]
  * attached.
  */
-public fun hasClickable(clickable: Clickable): LayoutElementMatcher =
-    LayoutElementMatcher("has $clickable") {
-        it.modifiers?.clickable?.toProto() == clickable.toProto()
+@JvmOverloads
+public fun hasClickable(
+    action: Action = loadAction(),
+    id: String? = null,
+    @Dimension(DP) minClickableWidth: Float = Float.NaN,
+    @Dimension(DP) minClickableHeight: Float = Float.NaN
+): LayoutElementMatcher =
+    LayoutElementMatcher("has clickable($action, $id, $minClickableWidth, $minClickableHeight)") {
+        val clk = it.modifiers?.clickable ?: return@LayoutElementMatcher false
+        if (!minClickableWidth.isNaN() && clk.minimumClickableWidth.value != minClickableWidth) {
+            return@LayoutElementMatcher false
+        }
+        if (!minClickableHeight.isNaN() && clk.minimumClickableHeight.value != minClickableHeight) {
+            return@LayoutElementMatcher false
+        }
+        clk.onClick?.toActionProto() == action.toActionProto() && id?.let { clk.id == it } != false
     }
 
 /**
diff --git a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
index ba732a8..84b7810 100644
--- a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
+++ b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.wear.protolayout.ActionBuilders.LoadAction
 import androidx.wear.protolayout.ColorBuilders.ColorProp
 import androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp
 import androidx.wear.protolayout.DimensionBuilders.dp
@@ -36,10 +35,10 @@
 import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
 import androidx.wear.protolayout.ModifiersBuilders.Modifiers
 import androidx.wear.protolayout.ModifiersBuilders.Semantics
-import androidx.wear.protolayout.StateBuilders
 import androidx.wear.protolayout.TypeBuilders.StringProp
 import androidx.wear.protolayout.expression.DynamicBuilders
 import androidx.wear.protolayout.layout.basicText
+import androidx.wear.protolayout.modifiers.loadAction
 import androidx.wear.protolayout.types.LayoutString
 import androidx.wear.protolayout.types.asLayoutConstraint
 import androidx.wear.protolayout.types.layoutString
@@ -70,32 +69,25 @@
 
     @Test
     fun hasClickable_matches() {
-        val clickable = Clickable.Builder().setOnClick(LoadAction.Builder().build()).build()
+        val clickable = Clickable.Builder().setOnClick(loadAction()).build()
         val testElement =
             Column.Builder()
                 .setModifiers(Modifiers.Builder().setClickable(clickable).build())
                 .build()
 
-        assertThat(hasClickable(clickable).matches(testElement)).isTrue()
+        assertThat(hasClickable().matches(testElement)).isTrue()
     }
 
     @Test
     fun hasClickable_doesNotMatch() {
-        val clickable = Clickable.Builder().setOnClick(LoadAction.Builder().build()).build()
-        val otherClickable =
-            Clickable.Builder()
-                .setOnClick(
-                    LoadAction.Builder()
-                        .setRequestState(StateBuilders.State.Builder().build())
-                        .build()
-                )
-                .build()
+        val clickable = Clickable.Builder().setOnClick(loadAction()).build()
+        val action = loadAction {}
         val testElement =
             Column.Builder()
                 .setModifiers(Modifiers.Builder().setClickable(clickable).build())
                 .build()
 
-        assertThat(hasClickable(otherClickable).matches(testElement)).isFalse()
+        assertThat(hasClickable(action = action).matches(testElement)).isFalse()
     }
 
     @Test
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 73620fa..b58a040 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -1492,6 +1492,30 @@
 
 package androidx.wear.protolayout.modifiers {
 
+  public final class BackgroundKt {
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier background(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.types.LayoutColor color, optional androidx.wear.protolayout.ModifiersBuilders.Corner? corner);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier backgroundColor(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.types.LayoutColor color);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Corner corner);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float cornerRadius);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipBottomLeft(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipBottomRight(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipTopLeft(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipTopRight(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+  }
+
+  public final class ClickableKt {
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable();
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableWidth);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableHeight);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clickable(androidx.wear.protolayout.modifiers.LayoutModifier, optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clickable(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Clickable clickable);
+    method public static androidx.wear.protolayout.ActionBuilders.LoadAction loadAction(optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.StateBuilders.State.Builder,kotlin.Unit>? requestedState);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static androidx.wear.protolayout.modifiers.LayoutModifier minimumTouchTargetSize(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minWidth, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minHeight);
+  }
+
   public interface LayoutModifier {
     method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.wear.protolayout.modifiers.LayoutModifier.Element,? extends R> operation);
     method public default infix androidx.wear.protolayout.modifiers.LayoutModifier then(androidx.wear.protolayout.modifiers.LayoutModifier other);
@@ -1510,6 +1534,16 @@
     method public static androidx.wear.protolayout.ModifiersBuilders.Modifiers toProtoLayoutModifiers(androidx.wear.protolayout.modifiers.LayoutModifier);
   }
 
+  public final class PaddingKt {
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Padding padding);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float all);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float horizontal, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float vertical);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float start, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float top, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float end, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float bottom, optional boolean rtlAware);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(@Dimension(unit=androidx.annotation.Dimension.Companion.DP) float all);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(@Dimension(unit=androidx.annotation.Dimension.Companion.DP) float horizontal, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float vertical);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float start, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float top, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float end, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float bottom, optional boolean rtlAware);
+  }
+
   public final class SemanticsKt {
     method public static androidx.wear.protolayout.modifiers.LayoutModifier contentDescription(androidx.wear.protolayout.modifiers.LayoutModifier, String staticValue, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) androidx.wear.protolayout.expression.DynamicBuilders.DynamicString? dynamicValue);
     method public static androidx.wear.protolayout.modifiers.LayoutModifier semanticsRole(androidx.wear.protolayout.modifiers.LayoutModifier, int semanticsRole);
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 73620fa..b58a040 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -1492,6 +1492,30 @@
 
 package androidx.wear.protolayout.modifiers {
 
+  public final class BackgroundKt {
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier background(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.types.LayoutColor color, optional androidx.wear.protolayout.ModifiersBuilders.Corner? corner);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier backgroundColor(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.types.LayoutColor color);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Corner corner);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float cornerRadius);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clip(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipBottomLeft(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipBottomRight(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipTopLeft(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static androidx.wear.protolayout.modifiers.LayoutModifier clipTopRight(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float x, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float y);
+  }
+
+  public final class ClickableKt {
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable();
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableWidth);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Clickable clickable(optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) float minClickableHeight);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clickable(androidx.wear.protolayout.modifiers.LayoutModifier, optional androidx.wear.protolayout.ActionBuilders.Action action, optional String? id);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier clickable(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Clickable clickable);
+    method public static androidx.wear.protolayout.ActionBuilders.LoadAction loadAction(optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.StateBuilders.State.Builder,kotlin.Unit>? requestedState);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static androidx.wear.protolayout.modifiers.LayoutModifier minimumTouchTargetSize(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minWidth, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float minHeight);
+  }
+
   public interface LayoutModifier {
     method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.wear.protolayout.modifiers.LayoutModifier.Element,? extends R> operation);
     method public default infix androidx.wear.protolayout.modifiers.LayoutModifier then(androidx.wear.protolayout.modifiers.LayoutModifier other);
@@ -1510,6 +1534,16 @@
     method public static androidx.wear.protolayout.ModifiersBuilders.Modifiers toProtoLayoutModifiers(androidx.wear.protolayout.modifiers.LayoutModifier);
   }
 
+  public final class PaddingKt {
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, androidx.wear.protolayout.ModifiersBuilders.Padding padding);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float all);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float horizontal, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float vertical);
+    method public static androidx.wear.protolayout.modifiers.LayoutModifier padding(androidx.wear.protolayout.modifiers.LayoutModifier, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float start, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float top, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float end, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float bottom, optional boolean rtlAware);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(@Dimension(unit=androidx.annotation.Dimension.Companion.DP) float all);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(@Dimension(unit=androidx.annotation.Dimension.Companion.DP) float horizontal, @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float vertical);
+    method public static androidx.wear.protolayout.ModifiersBuilders.Padding padding(optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float start, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float top, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float end, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float bottom, optional boolean rtlAware);
+  }
+
   public final class SemanticsKt {
     method public static androidx.wear.protolayout.modifiers.LayoutModifier contentDescription(androidx.wear.protolayout.modifiers.LayoutModifier, String staticValue, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) androidx.wear.protolayout.expression.DynamicBuilders.DynamicString? dynamicValue);
     method public static androidx.wear.protolayout.modifiers.LayoutModifier semanticsRole(androidx.wear.protolayout.modifiers.LayoutModifier, int semanticsRole);
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Background.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Background.kt
new file mode 100644
index 0000000..045a971
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Background.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.protolayout.modifiers
+
+import android.annotation.SuppressLint
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.ModifiersBuilders.Background
+import androidx.wear.protolayout.ModifiersBuilders.Corner
+import androidx.wear.protolayout.ModifiersBuilders.CornerRadius
+import androidx.wear.protolayout.expression.RequiresSchemaVersion
+import androidx.wear.protolayout.types.LayoutColor
+import androidx.wear.protolayout.types.cornerRadius
+import androidx.wear.protolayout.types.dp
+
+/** Sets the background color to [color]. */
+fun LayoutModifier.backgroundColor(color: LayoutColor): LayoutModifier =
+    this then BaseBackgroundElement(color)
+
+/**
+ * Sets the background color and clipping.
+ *
+ * @param color for the background
+ * @param corner to use for clipping the background
+ */
+fun LayoutModifier.background(color: LayoutColor, corner: Corner? = null): LayoutModifier =
+    this then BaseBackgroundElement(color).apply { corner?.let { clip(it) } }
+
+/** Clips the element to a rounded rectangle with four corners with [cornerRadius] radius. */
+fun LayoutModifier.clip(@Dimension(DP) cornerRadius: Float): LayoutModifier =
+    this then BaseCornerElement(cornerRadius)
+
+/**
+ * Clips the element to a rounded shape with [x] as the radius on the horizontal axis and [y] as the
+ * radius on the vertical axis for the four corners.
+ */
+@RequiresSchemaVersion(major = 1, minor = 400)
+fun LayoutModifier.clip(@Dimension(DP) x: Float, @Dimension(DP) y: Float): LayoutModifier {
+    val r = cornerRadius(x, y)
+    return this then
+        BaseCornerElement(
+            topLeftRadius = r,
+            topRightRadius = r,
+            bottomLeftRadius = r,
+            bottomRightRadius = r
+        )
+}
+
+/** Clips the element to a rounded rectangle with corners specified in [corner]. */
+fun LayoutModifier.clip(corner: Corner): LayoutModifier =
+    this then
+        BaseCornerElement(
+            cornerRadiusDp = corner.radius?.value,
+            topLeftRadius = corner.topLeftRadius,
+            topRightRadius = corner.topRightRadius,
+            bottomLeftRadius = corner.bottomLeftRadius,
+            bottomRightRadius = corner.bottomRightRadius
+        )
+
+/**
+ * Clips the top left corner of the element with [x] as the radius on the horizontal axis and [y] as
+ * the radius on the vertical axis.
+ */
+@RequiresSchemaVersion(major = 1, minor = 400)
+fun LayoutModifier.clipTopLeft(
+    @Dimension(DP) x: Float,
+    @Dimension(DP) y: Float = x
+): LayoutModifier = this then BaseCornerElement(topLeftRadius = cornerRadius(x, y))
+
+/**
+ * Clips the top right corner of the element with [x] as the radius on the horizontal axis and [y]
+ * as the radius on the vertical axis.
+ */
+@RequiresSchemaVersion(major = 1, minor = 400)
+fun LayoutModifier.clipTopRight(
+    @Dimension(DP) x: Float,
+    @Dimension(DP) y: Float = x
+): LayoutModifier = this then BaseCornerElement(topRightRadius = cornerRadius(x, y))
+
+/**
+ * Clips the bottom left corner of the element with [x] as the radius on the horizontal axis and [y]
+ * as the radius on the vertical axis.
+ */
+@RequiresSchemaVersion(major = 1, minor = 400)
+fun LayoutModifier.clipBottomLeft(
+    @Dimension(DP) x: Float,
+    @Dimension(DP) y: Float = x
+): LayoutModifier = this then BaseCornerElement(bottomLeftRadius = cornerRadius(x, y))
+
+/**
+ * Clips the bottom right corner of the element with [x] as the radius on the horizontal axis and
+ * [y] as the radius on the vertical axis.
+ */
+@RequiresSchemaVersion(major = 1, minor = 400)
+fun LayoutModifier.clipBottomRight(
+    @Dimension(DP) x: Float,
+    @Dimension(DP) y: Float = x
+): LayoutModifier = this then BaseCornerElement(bottomRightRadius = cornerRadius(x, y))
+
+internal class BaseBackgroundElement(val color: LayoutColor) : LayoutModifier.Element {
+    fun foldIn(initial: Background.Builder?): Background.Builder =
+        (initial ?: Background.Builder()).setColor(color.prop)
+}
+
+internal class BaseCornerElement(
+    val cornerRadiusDp: Float? = null,
+    @RequiresSchemaVersion(major = 1, minor = 400) val topLeftRadius: CornerRadius? = null,
+    @RequiresSchemaVersion(major = 1, minor = 400) val topRightRadius: CornerRadius? = null,
+    @RequiresSchemaVersion(major = 1, minor = 400) val bottomLeftRadius: CornerRadius? = null,
+    @RequiresSchemaVersion(major = 1, minor = 400) val bottomRightRadius: CornerRadius? = null
+) : LayoutModifier.Element {
+    @SuppressLint("ProtoLayoutMinSchema")
+    fun foldIn(initial: Corner.Builder?): Corner.Builder =
+        (initial ?: Corner.Builder()).apply {
+            cornerRadiusDp?.let { setRadius(cornerRadiusDp.dp) }
+            topLeftRadius?.let { setTopLeftRadius(cornerRadius(it.x.value, it.y.value)) }
+            topRightRadius?.let { setTopRightRadius(cornerRadius(it.x.value, it.y.value)) }
+            bottomLeftRadius?.let { setBottomLeftRadius(cornerRadius(it.x.value, it.y.value)) }
+            bottomRightRadius?.let { setBottomRightRadius(cornerRadius(it.x.value, it.y.value)) }
+        }
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Clickable.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Clickable.kt
new file mode 100644
index 0000000..da4d74f
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Clickable.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.wear.protolayout.modifiers
+
+import android.annotation.SuppressLint
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.ActionBuilders.Action
+import androidx.wear.protolayout.ActionBuilders.LoadAction
+import androidx.wear.protolayout.ActionBuilders.actionFromProto
+import androidx.wear.protolayout.ModifiersBuilders.Clickable
+import androidx.wear.protolayout.StateBuilders.State
+import androidx.wear.protolayout.expression.RequiresSchemaVersion
+import androidx.wear.protolayout.types.dp
+
+/**
+ * Adds the clickable property of the modified element. It allows the modified element to have
+ * actions associated with it, which will be executed when the element is tapped.
+ *
+ * @param action is triggered whenever the element is tapped. By default adds an empty [LoadAction].
+ * @param id is the associated identifier for this clickable. This will be passed to the action
+ *   handler.
+ */
+fun LayoutModifier.clickable(action: Action = loadAction(), id: String? = null): LayoutModifier =
+    this then BaseClickableElement(action = action, id = id)
+
+/**
+ * Creates a [Clickable] that allows the modified element to have actions associated with it, which
+ * will be executed when the element is tapped.
+ *
+ * @param action is triggered whenever the element is tapped. By default adds an empty [LoadAction].
+ * @param id is the associated identifier for this clickable. This will be passed to the action
+ *   handler.
+ * @param minClickableWidth of the clickable area. The default value is 48dp, following the Material
+ *   design accessibility guideline. Note that this value does not affect the layout, so the minimum
+ *   clickable width is not guaranteed unless there is enough space around the element within its
+ *   parent bounds.
+ * @param minClickableHeight of the clickable area. The default value is 48dp, following the
+ *   Material design accessibility guideline. Note that this value does not affect the layout, so
+ *   the minimum clickable height is not guaranteed unless there is enough space around the element
+ *   within its parent bounds.
+ */
+@SuppressLint("ProtoLayoutMinSchema")
+@JvmOverloads
+fun clickable(
+    action: Action = loadAction(),
+    id: String? = null,
+    @RequiresSchemaVersion(major = 1, minor = 300)
+    @Dimension(DP)
+    minClickableWidth: Float = Float.NaN,
+    @RequiresSchemaVersion(major = 1, minor = 300)
+    @Dimension(DP)
+    minClickableHeight: Float = Float.NaN
+): Clickable =
+    Clickable.Builder()
+        .setOnClick(action)
+        .apply {
+            id?.let { setId(it) }
+            if (!minClickableWidth.isNaN()) setMinimumClickableWidth(minClickableWidth.dp)
+            if (!minClickableHeight.isNaN()) setMinimumClickableHeight(minClickableHeight.dp)
+        }
+        .build()
+
+/**
+ * Adds the clickable property of the modified element. It allows the modified element to have
+ * actions associated with it, which will be executed when the element is tapped.
+ */
+fun LayoutModifier.clickable(clickable: Clickable): LayoutModifier =
+    this then
+        BaseClickableElement(
+            action =
+                clickable.onClick?.let {
+                    actionFromProto(it.toActionProto(), checkNotNull(clickable.fingerprint))
+                },
+            id = clickable.id,
+            minClickableWidth = clickable.minimumClickableWidth.value,
+            minClickableHeight = clickable.minimumClickableHeight.value
+        )
+
+/**
+ * Creates an action used to load (or reload) the layout contents.
+ *
+ * @param requestedState is the [State] associated with this action. This state will be passed to
+ *   the action handler.
+ */
+fun loadAction(requestedState: (State.Builder.() -> Unit)? = null): LoadAction =
+    LoadAction.Builder()
+        .apply { requestedState?.let { this.setRequestState(State.Builder().apply(it).build()) } }
+        .build()
+
+/**
+ * Sets the minimum width and height of the clickable area. The default value is 48dp, following the
+ * Material design accessibility guideline. Note that this value does not affect the layout, so the
+ * minimum clickable width/height is not guaranteed unless there is enough space around the element
+ * within its parent bounds.
+ */
+@RequiresSchemaVersion(major = 1, minor = 300)
+fun LayoutModifier.minimumTouchTargetSize(
+    @Dimension(DP) minWidth: Float,
+    @Dimension(DP) minHeight: Float
+): LayoutModifier =
+    this then BaseClickableElement(minClickableWidth = minWidth, minClickableHeight = minHeight)
+
+internal class BaseClickableElement(
+    val action: Action? = null,
+    val id: String? = null,
+    @Dimension(DP) val minClickableWidth: Float = Float.NaN,
+    @Dimension(DP) val minClickableHeight: Float = Float.NaN,
+) : LayoutModifier.Element {
+    @SuppressLint("ProtoLayoutMinSchema")
+    fun foldIn(initial: Clickable.Builder?): Clickable.Builder =
+        (initial ?: Clickable.Builder()).apply {
+            if (!id.isNullOrEmpty()) setId(id)
+            action?.let { setOnClick(it) }
+            if (!minClickableWidth.isNaN()) setMinimumClickableWidth(minClickableWidth.dp)
+            if (!minClickableHeight.isNaN()) setMinimumClickableHeight(minClickableHeight.dp)
+        }
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Metadata.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Metadata.kt
new file mode 100644
index 0000000..9c0f8ca
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Metadata.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:RestrictTo(Scope.LIBRARY_GROUP)
+
+package androidx.wear.protolayout.modifiers
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
+
+/**
+ * Applies additional metadata about an element. This is meant to be used by libraries building
+ * higher-level components. This can be used to track component metadata.
+ */
+fun LayoutModifier.tag(tagData: ByteArray): LayoutModifier =
+    this then BaseMetadataElement(tagData = tagData)
+
+/**
+ * Applies additional metadata about an element. This is meant to be used by libraries building
+ * higher-level components. This can be used to track component metadata.
+ */
+fun LayoutModifier.tag(tag: String): LayoutModifier = tag(tag.toByteArray())
+
+internal class BaseMetadataElement(val tagData: ByteArray) : LayoutModifier.Element {
+    fun foldIn(initial: ElementMetadata.Builder?): ElementMetadata.Builder =
+        (initial ?: ElementMetadata.Builder()).setTagData(tagData)
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/ModifierAppliers.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/ModifierAppliers.kt
index 194809c..0b5531a 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/ModifierAppliers.kt
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/ModifierAppliers.kt
@@ -16,29 +16,43 @@
 
 package androidx.wear.protolayout.modifiers
 
-import androidx.annotation.RestrictTo
 import androidx.wear.protolayout.ModifiersBuilders
+import androidx.wear.protolayout.ModifiersBuilders.Background
+import androidx.wear.protolayout.ModifiersBuilders.Clickable
+import androidx.wear.protolayout.ModifiersBuilders.Corner
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
+import androidx.wear.protolayout.ModifiersBuilders.Padding
 import androidx.wear.protolayout.ModifiersBuilders.Semantics
 
 /** Creates a [ModifiersBuilders.Modifiers] from a [LayoutModifier]. */
-fun LayoutModifier.toProtoLayoutModifiers(): ModifiersBuilders.Modifiers =
-    toProtoLayoutModifiersBuilder().build()
+fun LayoutModifier.toProtoLayoutModifiers(): ModifiersBuilders.Modifiers {
+    var semantics: Semantics.Builder? = null
+    var background: Background.Builder? = null
+    var corners: Corner.Builder? = null
+    var clickable: Clickable.Builder? = null
+    var padding: Padding.Builder? = null
+    var metadata: ElementMetadata.Builder? = null
 
-// TODO: b/384921198 - Remove when M3 elements can use LayoutModifier chain for everything.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-/** Creates a [ModifiersBuilders.Modifiers.Builder] from a [LayoutModifier]. */
-fun LayoutModifier.toProtoLayoutModifiersBuilder(): ModifiersBuilders.Modifiers.Builder {
-    data class AccumulatingModifier(val semantics: Semantics.Builder? = null)
-
-    val accumulatingModifier =
-        this.foldIn(AccumulatingModifier()) { acc, e ->
-            when (e) {
-                is BaseSemanticElement -> AccumulatingModifier(semantics = e.foldIn(acc.semantics))
-                else -> acc
-            }
+    this.foldIn(Unit) { _, e ->
+        when (e) {
+            is BaseSemanticElement -> semantics = e.foldIn(semantics)
+            is BaseBackgroundElement -> background = e.foldIn(background)
+            is BaseCornerElement -> corners = e.foldIn(corners)
+            is BaseClickableElement -> clickable = e.foldIn(clickable)
+            is BasePaddingElement -> padding = e.foldIn(padding)
+            is BaseMetadataElement -> metadata = e.foldIn(metadata)
         }
-
-    return ModifiersBuilders.Modifiers.Builder().apply {
-        accumulatingModifier.semantics?.let { setSemantics(it.build()) }
     }
+
+    corners?.let { background = (background ?: Background.Builder()).setCorner(it.build()) }
+
+    return ModifiersBuilders.Modifiers.Builder()
+        .apply {
+            semantics?.let { setSemantics(it.build()) }
+            background?.let { setBackground(it.build()) }
+            clickable?.let { setClickable(it.build()) }
+            padding?.let { setPadding(it.build()) }
+            metadata?.let { setMetadata(it.build()) }
+        }
+        .build()
 }
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Padding.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Padding.kt
new file mode 100644
index 0000000..050b8ce
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Padding.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.protolayout.modifiers
+
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.ModifiersBuilders.Padding
+import androidx.wear.protolayout.types.dp
+
+/**
+ * Applies [all] dp of additional space along each edge of the content, left, top, right and bottom.
+ */
+fun LayoutModifier.padding(@Dimension(DP) all: Float): LayoutModifier =
+    this then BasePaddingElement(start = all, top = all, end = all, bottom = all, rtlAware = false)
+
+/**
+ * Creates a [Padding] that applies [all] dp of additional space along each edge of the content,
+ * left, top, right and bottom.
+ */
+fun padding(@Dimension(DP) all: Float): Padding = Padding.Builder().setAll(all.dp).build()
+
+/**
+ * Applies [horizontal] dp of additional space along the left and right edges of the content and
+ * [vertical] dp of additional space along the top and bottom edges of the content.
+ */
+fun LayoutModifier.padding(
+    @Dimension(DP) horizontal: Float,
+    @Dimension(DP) vertical: Float
+): LayoutModifier = padding(horizontal, vertical, horizontal, vertical, rtlAware = false)
+
+/**
+ * Creates a [Padding] that applies [horizontal] dp of additional space along the left and right
+ * edges of the content and [vertical] dp of additional space along the top and bottom edges of the
+ * content.
+ */
+fun padding(@Dimension(DP) horizontal: Float, @Dimension(DP) vertical: Float): Padding =
+    padding(horizontal, vertical, horizontal, vertical)
+
+/**
+ * Applies additional space along each edge of the content in [DP]: [start], [top], [end] and
+ * [bottom]
+ *
+ * @param start The padding on the start of the content, depending on the layout direction, in [DP]
+ *   and the value of [rtlAware].
+ * @param top The padding at the top, in [DP].
+ * @param end The padding on the end of the content, depending on the layout direction, in [DP] and
+ *   the value of [rtlAware].
+ * @param bottom The padding at the bottom, in [DP].
+ * @param rtlAware specifies whether the [start]/[end] padding is aware of RTL support. If `true`,
+ *   the values for [start]/[end] will follow the layout direction (i.e. [start] will refer to the
+ *   right hand side of the container if the device is using an RTL locale). If `false`,
+ *   [start]/[end] will always map to left/right, accordingly.
+ */
+fun LayoutModifier.padding(
+    @Dimension(DP) start: Float = Float.NaN,
+    @Dimension(DP) top: Float = Float.NaN,
+    @Dimension(DP) end: Float = Float.NaN,
+    @Dimension(DP) bottom: Float = Float.NaN,
+    rtlAware: Boolean = true
+): LayoutModifier =
+    this then
+        BasePaddingElement(
+            start = start,
+            top = top,
+            end = end,
+            bottom = bottom,
+            rtlAware = rtlAware
+        )
+
+/** Applies additional space along each edge of the content. */
+fun LayoutModifier.padding(padding: Padding): LayoutModifier =
+    padding(
+        start = padding.start?.value ?: Float.NaN,
+        top = padding.top?.value ?: Float.NaN,
+        end = padding.end?.value ?: Float.NaN,
+        bottom = padding.bottom?.value ?: Float.NaN
+    )
+
+/**
+ * Creates a [Padding] that applies additional space along each edge of the content in [DP]:
+ * [start], [top], [end] and [bottom]
+ *
+ * @param start The padding on the start of the content, depending on the layout direction, in [DP]
+ *   and the value of [rtlAware].
+ * @param top The padding at the top, in [DP].
+ * @param end The padding on the end of the content, depending on the layout direction, in [DP] and
+ *   the value of [rtlAware].
+ * @param bottom The padding at the bottom, in [DP].
+ * @param rtlAware specifies whether the [start]/[end] padding is aware of RTL support. If `true`,
+ *   the values for [start]/[end] will follow the layout direction (i.e. [start] will refer to the
+ *   right hand side of the container if the device is using an RTL locale). If `false`,
+ *   [start]/[end] will always map to left/right, accordingly.
+ */
+@Suppress("MissingJvmstatic") // Conflicts with the other overloads
+fun padding(
+    @Dimension(DP) start: Float = Float.NaN,
+    @Dimension(DP) top: Float = Float.NaN,
+    @Dimension(DP) end: Float = Float.NaN,
+    @Dimension(DP) bottom: Float = Float.NaN,
+    rtlAware: Boolean = true
+): Padding =
+    Padding.Builder()
+        .apply {
+            if (!start.isNaN()) {
+                setStart(start.dp)
+            }
+            if (!top.isNaN()) {
+                setTop(top.dp)
+            }
+            if (!end.isNaN()) {
+                setEnd(end.dp)
+            }
+            if (!bottom.isNaN()) {
+                setBottom(bottom.dp)
+            }
+        }
+        .setRtlAware(rtlAware)
+        .build()
+
+internal class BasePaddingElement(
+    val start: Float = Float.NaN,
+    val top: Float = Float.NaN,
+    val end: Float = Float.NaN,
+    val bottom: Float = Float.NaN,
+    val rtlAware: Boolean = true
+) : LayoutModifier.Element {
+
+    fun foldIn(initial: Padding.Builder?): Padding.Builder =
+        (initial ?: Padding.Builder()).apply {
+            if (!start.isNaN()) {
+                setStart(start.dp)
+            }
+            if (!top.isNaN()) {
+                setTop(top.dp)
+            }
+            if (!end.isNaN()) {
+                setEnd(end.dp)
+            }
+            if (!bottom.isNaN()) {
+                setBottom(bottom.dp)
+            }
+            setRtlAware(rtlAware)
+        }
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
index 3bf0caf..d35b071 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
@@ -25,30 +25,6 @@
 import androidx.wear.protolayout.expression.RequiresSchemaVersion
 import java.util.Objects
 
-internal class BaseSemanticElement(
-    val contentDescription: StringProp? = null,
-    @SemanticsRole val semanticsRole: Int = SEMANTICS_ROLE_NONE
-) : LayoutModifier.Element {
-    @SuppressLint("ProtoLayoutMinSchema")
-    fun foldIn(initial: Semantics.Builder?): Semantics.Builder =
-        (initial ?: Semantics.Builder()).apply {
-            contentDescription?.let { setContentDescription(it) }
-            if (semanticsRole != SEMANTICS_ROLE_NONE) {
-                setRole(semanticsRole)
-            }
-        }
-
-    override fun equals(other: Any?): Boolean =
-        other is BaseSemanticElement &&
-            contentDescription == other.contentDescription &&
-            semanticsRole == other.semanticsRole
-
-    override fun hashCode(): Int = Objects.hash(contentDescription, semanticsRole)
-
-    override fun toString(): String =
-        "BaseSemanticElement[contentDescription=$contentDescription, semanticRole=$semanticsRole"
-}
-
 /**
  * Adds content description to be read by Talkback.
  *
@@ -76,3 +52,27 @@
  */
 fun LayoutModifier.semanticsRole(@SemanticsRole semanticsRole: Int): LayoutModifier =
     this then BaseSemanticElement(semanticsRole = semanticsRole)
+
+internal class BaseSemanticElement(
+    val contentDescription: StringProp? = null,
+    @SemanticsRole val semanticsRole: Int = SEMANTICS_ROLE_NONE
+) : LayoutModifier.Element {
+    @SuppressLint("ProtoLayoutMinSchema")
+    fun foldIn(initial: Semantics.Builder?): Semantics.Builder =
+        (initial ?: Semantics.Builder()).apply {
+            contentDescription?.let { setContentDescription(it) }
+            if (semanticsRole != SEMANTICS_ROLE_NONE) {
+                setRole(semanticsRole)
+            }
+        }
+
+    override fun equals(other: Any?): Boolean =
+        other is BaseSemanticElement &&
+            contentDescription == other.contentDescription &&
+            semanticsRole == other.semanticsRole
+
+    override fun hashCode(): Int = Objects.hash(contentDescription, semanticsRole)
+
+    override fun toString(): String =
+        "BaseSemanticElement[contentDescription=$contentDescription, semanticRole=$semanticsRole"
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
index 2f4295e..ef30cd7 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
@@ -16,9 +16,12 @@
 
 package androidx.wear.protolayout.types
 
+import androidx.wear.protolayout.DimensionBuilders.DpProp
 import androidx.wear.protolayout.DimensionBuilders.EmProp
 import androidx.wear.protolayout.DimensionBuilders.SpProp
+import androidx.wear.protolayout.ModifiersBuilders
 import androidx.wear.protolayout.TypeBuilders.BoolProp
+import androidx.wear.protolayout.expression.RequiresSchemaVersion
 
 internal val Float.sp: SpProp
     get() = SpProp.Builder().setValue(this).build()
@@ -28,3 +31,10 @@
 
 internal val Boolean.prop: BoolProp
     get() = BoolProp.Builder(this).build()
+
+internal val Float.dp: DpProp
+    get() = DpProp.Builder(this).build()
+
+@RequiresSchemaVersion(major = 1, minor = 400)
+internal fun cornerRadius(x: Float, y: Float) =
+    ModifiersBuilders.CornerRadius.Builder(x.dp, y.dp).build()
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/modifiers/ModifiersTest.kt b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/modifiers/ModifiersTest.kt
index f91621c..fdc7f7f 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/modifiers/ModifiersTest.kt
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/modifiers/ModifiersTest.kt
@@ -16,10 +16,16 @@
 
 package androidx.wear.protolayout.modifiers
 
+import android.graphics.Color
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.wear.protolayout.ActionBuilders.LoadAction
 import androidx.wear.protolayout.ModifiersBuilders.SEMANTICS_ROLE_BUTTON
 import androidx.wear.protolayout.ModifiersBuilders.SEMANTICS_ROLE_NONE
-import androidx.wear.protolayout.expression.DynamicBuilders
+import androidx.wear.protolayout.expression.AppDataKey
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
+import androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue
+import androidx.wear.protolayout.types.LayoutColor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,8 +74,174 @@
         assertThat(modifiers.semantics?.role).isEqualTo(SEMANTICS_ROLE_BUTTON)
     }
 
+    @Test
+    fun background_clip_toModifier() {
+        val modifiers =
+            LayoutModifier.background(COLOR)
+                .clip(CORNER_RADIUS)
+                .clip(CORNER_RADIUS_X, CORNER_RADIUS_Y)
+                .clipTopRight(0f, 0f)
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.background?.color?.argb).isEqualTo(COLOR.prop.argb)
+        assertThat(modifiers.background?.corner?.radius?.value).isEqualTo(CORNER_RADIUS)
+        assertThat(modifiers.background?.corner?.topLeftRadius?.x?.value).isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.topLeftRadius?.y?.value).isEqualTo(CORNER_RADIUS_Y)
+        assertThat(modifiers.background?.corner?.topRightRadius?.x?.value).isEqualTo(0f)
+        assertThat(modifiers.background?.corner?.topRightRadius?.y?.value).isEqualTo(0f)
+        assertThat(modifiers.background?.corner?.bottomLeftRadius?.x?.value)
+            .isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.bottomLeftRadius?.y?.value)
+            .isEqualTo(CORNER_RADIUS_Y)
+        assertThat(modifiers.background?.corner?.bottomRightRadius?.x?.value)
+            .isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.bottomRightRadius?.y?.value)
+            .isEqualTo(CORNER_RADIUS_Y)
+    }
+
+    @Test
+    fun perCornerClip_clip_overwritesAllCorners() {
+        val modifiers =
+            LayoutModifier.clipTopLeft(0f, 1f)
+                .clipTopRight(2f, 3f)
+                .clipBottomLeft(4f, 5f)
+                .clipBottomRight(6f, 7f)
+                .clip(CORNER_RADIUS_X, CORNER_RADIUS_Y)
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.background?.color).isNull()
+        assertThat(modifiers.background?.corner?.radius?.value).isEqualTo(null)
+        assertThat(modifiers.background?.corner?.topLeftRadius?.x?.value).isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.topLeftRadius?.y?.value).isEqualTo(CORNER_RADIUS_Y)
+        assertThat(modifiers.background?.corner?.topRightRadius?.x?.value)
+            .isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.topRightRadius?.y?.value)
+            .isEqualTo(CORNER_RADIUS_Y)
+        assertThat(modifiers.background?.corner?.bottomLeftRadius?.x?.value)
+            .isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.bottomLeftRadius?.y?.value)
+            .isEqualTo(CORNER_RADIUS_Y)
+        assertThat(modifiers.background?.corner?.bottomRightRadius?.x?.value)
+            .isEqualTo(CORNER_RADIUS_X)
+        assertThat(modifiers.background?.corner?.bottomRightRadius?.y?.value)
+            .isEqualTo(CORNER_RADIUS_Y)
+    }
+
+    @Test
+    fun clickable_toModifier() {
+        val id = "ID"
+        val minTouchWidth = 51f
+        val minTouchHeight = 52f
+        val statePair1 = Pair(AppDataKey<DynamicInt32>("Int"), DynamicDataValue.fromInt(42))
+        val statePair2 =
+            Pair(AppDataKey<DynamicString>("String"), DynamicDataValue.fromString("42"))
+
+        val modifiers =
+            LayoutModifier.clickable(
+                    loadAction {
+                        addKeyToValueMapping(statePair1.first, statePair1.second)
+                        addKeyToValueMapping(statePair2.first, statePair2.second)
+                    },
+                    id
+                )
+                .minimumTouchTargetSize(minTouchWidth, minTouchHeight)
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.clickable?.id).isEqualTo(id)
+        assertThat(modifiers.clickable?.minimumClickableWidth?.value).isEqualTo(minTouchWidth)
+        assertThat(modifiers.clickable?.minimumClickableHeight?.value).isEqualTo(minTouchHeight)
+        assertThat(modifiers.clickable?.onClick).isInstanceOf(LoadAction::class.java)
+        val action = modifiers.clickable?.onClick as LoadAction
+        assertThat(action.requestState?.keyToValueMapping)
+            .containsExactlyEntriesIn(mapOf(statePair1, statePair2))
+    }
+
+    @Test
+    fun clickable_fromProto_toModifier() {
+        val id = "ID"
+        val minTouchWidth = 51f
+        val minTouchHeight = 52f
+        val statePair1 = Pair(AppDataKey<DynamicInt32>("Int"), DynamicDataValue.fromInt(42))
+        val statePair2 =
+            Pair(AppDataKey<DynamicString>("String"), DynamicDataValue.fromString("42"))
+
+        val modifiers =
+            LayoutModifier.clickable(
+                    clickable(
+                        loadAction {
+                            addKeyToValueMapping(statePair1.first, statePair1.second)
+                            addKeyToValueMapping(statePair2.first, statePair2.second)
+                        },
+                        id = id,
+                        minClickableWidth = minTouchWidth,
+                        minClickableHeight = minTouchHeight
+                    )
+                )
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.clickable?.id).isEqualTo(id)
+        assertThat(modifiers.clickable?.minimumClickableWidth?.value).isEqualTo(minTouchWidth)
+        assertThat(modifiers.clickable?.minimumClickableHeight?.value).isEqualTo(minTouchHeight)
+        assertThat(modifiers.clickable?.onClick).isInstanceOf(LoadAction::class.java)
+        val action = modifiers.clickable?.onClick as LoadAction
+        assertThat(action.requestState?.keyToValueMapping)
+            .containsExactlyEntriesIn(mapOf(statePair1, statePair2))
+    }
+
+    @Test
+    fun padding_toModifier() {
+        val modifiers =
+            LayoutModifier.padding(PADDING_ALL)
+                .padding(bottom = BOTTOM_PADDING, rtlAware = false)
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.padding?.start?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.top?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.end?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.bottom?.value).isEqualTo(BOTTOM_PADDING)
+        assertThat(modifiers.padding?.rtlAware?.value).isFalse()
+    }
+
+    @Test
+    fun perSidePadding_padding_overwritesAllSides() {
+        val modifiers =
+            LayoutModifier.padding(
+                    start = START_PADDING,
+                    top = TOP_PADDING,
+                    end = END_PADDING,
+                    bottom = BOTTOM_PADDING,
+                    rtlAware = true
+                )
+                .padding(PADDING_ALL)
+                .toProtoLayoutModifiers()
+
+        assertThat(modifiers.padding?.start?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.top?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.end?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.bottom?.value).isEqualTo(PADDING_ALL)
+        assertThat(modifiers.padding?.rtlAware?.value).isFalse()
+    }
+
+    @Test
+    fun metadata_toModifier() {
+        val modifiers = LayoutModifier.tag(METADATA).toProtoLayoutModifiers()
+
+        assertThat(modifiers.metadata?.tagData).isEqualTo(METADATA_BYTE_ARRAY)
+    }
+
     companion object {
         const val STATIC_CONTENT_DESCRIPTION = "content desc"
-        val DYNAMIC_CONTENT_DESCRIPTION = DynamicBuilders.DynamicString.constant("dynamic content")
+        val DYNAMIC_CONTENT_DESCRIPTION = DynamicString.constant("dynamic content")
+        val COLOR = LayoutColor(Color.RED)
+        const val CORNER_RADIUS_X = 1.2f
+        const val CORNER_RADIUS_Y = 3.4f
+        const val CORNER_RADIUS = 5.6f
+        const val START_PADDING = 1f
+        const val TOP_PADDING = 2f
+        const val END_PADDING = 3f
+        const val BOTTOM_PADDING = 4f
+        const val PADDING_ALL = 5f
+        const val METADATA = "metadata"
+        val METADATA_BYTE_ARRAY = METADATA.toByteArray()
     }
 }
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/Helpers.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/Helpers.kt
deleted file mode 100644
index bc76b6c..0000000
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/Helpers.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.tiles.samples.tile
-
-import androidx.wear.protolayout.ActionBuilders.LoadAction
-import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.TypeBuilders.StringProp
-
-internal val EMPTY_LOAD_CLICKABLE =
-    Clickable.Builder().setOnClick(LoadAction.Builder().build()).build()
-
-internal fun String.prop(): StringProp = StringProp.Builder(this).build()
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index 1b5e62c..f7704fa 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -48,6 +48,7 @@
 import androidx.wear.protolayout.material3.textDataCard
 import androidx.wear.protolayout.material3.textEdgeButton
 import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.clickable
 import androidx.wear.protolayout.modifiers.contentDescription
 import androidx.wear.protolayout.types.layoutString
 import androidx.wear.tiles.RequestBuilders
@@ -122,7 +123,7 @@
             mainSlot = { oneSlotButtons() },
             bottomSlot = {
                 textEdgeButton(
-                    onClick = EMPTY_LOAD_CLICKABLE,
+                    onClick = clickable(),
                     modifier = LayoutModifier.contentDescription("EdgeButton"),
                 ) {
                     text("Edge".layoutString)
@@ -134,7 +135,7 @@
 private fun MaterialScope.oneSlotButtons() = buttonGroup {
     buttonGroupItem {
         iconButton(
-            onClick = EMPTY_LOAD_CLICKABLE,
+            onClick = clickable(),
             modifier = LayoutModifier.contentDescription("Icon button"),
             width = expand(),
             iconContent = { icon(ICON_ID) }
@@ -142,7 +143,7 @@
     }
     buttonGroupItem {
         iconButton(
-            onClick = EMPTY_LOAD_CLICKABLE,
+            onClick = clickable(),
             modifier = LayoutModifier.contentDescription("Icon button"),
             width = expand(),
             shape = shapes.large,
@@ -151,7 +152,7 @@
     }
     buttonGroupItem {
         textButton(
-            onClick = EMPTY_LOAD_CLICKABLE,
+            onClick = clickable(),
             modifier = LayoutModifier.contentDescription("Text button"),
             width = expand(),
             style = smallTextButtonStyle(),
@@ -163,7 +164,7 @@
 
 private fun MaterialScope.appCardSample() =
     appCard(
-        onClick = EMPTY_LOAD_CLICKABLE,
+        onClick = clickable(),
         modifier = LayoutModifier.contentDescription("Sample Card"),
         colors =
             CardColors(
@@ -199,7 +200,7 @@
 
 private fun MaterialScope.graphicDataCardSample() =
     graphicDataCard(
-        onClick = EMPTY_LOAD_CLICKABLE,
+        onClick = clickable(),
         modifier = LayoutModifier.contentDescription("Graphic Data Card"),
         height = expand(),
         horizontalAlignment = LayoutElementBuilders.HORIZONTAL_ALIGN_END,
@@ -234,7 +235,7 @@
 private fun MaterialScope.dataCards() = buttonGroup {
     buttonGroupItem {
         textDataCard(
-            onClick = EMPTY_LOAD_CLICKABLE,
+            onClick = clickable(),
             modifier = LayoutModifier.contentDescription("Data Card with icon"),
             width = weight(1f),
             height = expand(),
@@ -247,7 +248,7 @@
     }
     buttonGroupItem {
         iconDataCard(
-            onClick = EMPTY_LOAD_CLICKABLE,
+            onClick = clickable(),
             modifier =
                 LayoutModifier.contentDescription(
                     "Compact Data Card without icon or secondary label"
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index 3fe8986..9e58425 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -59,17 +59,18 @@
     method public static void apply(androidx.webkit.ProcessGlobalConfig);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDataDirectorySuffix(android.content.Context, String);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDirectoryBasePaths(android.content.Context, java.io.File, java.io.File);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setPartitionedCookiesEnabled(android.content.Context, boolean);
   }
 
   public interface Profile {
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void clearPrefetchAsync(String, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void clearPrefetchAsync(String, java.util.concurrent.Executor, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.CookieManager getCookieManager();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public String getName();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.ServiceWorkerController getServiceWorkerController();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.WebStorage getWebStorage();
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, androidx.core.os.CancellationSignal?, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, androidx.core.os.CancellationSignal?, androidx.webkit.SpeculativeLoadingParameters, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.webkit.SpeculativeLoadingParameters, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
     field public static final String DEFAULT_PROFILE_NAME = "Default";
   }
 
@@ -333,6 +334,13 @@
   @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.TYPE}) public static @interface WebSettingsCompat.ExperimentalSpeculativeLoading {
   }
 
+  public final class WebStorageCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static void deleteBrowsingData(android.webkit.WebStorage, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static void deleteBrowsingData(android.webkit.WebStorage, java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static String deleteBrowsingDataForSite(android.webkit.WebStorage, String, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static String deleteBrowsingDataForSite(android.webkit.WebStorage, String, java.util.concurrent.Executor, Runnable);
+  }
+
   public final class WebViewAssetLoader {
     method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
     field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
@@ -424,6 +432,7 @@
     field public static final String BACK_FORWARD_CACHE = "BACK_FORWARD_CACHE";
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DEFAULT_TRAFFICSTATS_TAGGING = "DEFAULT_TRAFFICSTATS_TAGGING";
+    field public static final String DELETE_BROWSING_DATA = "DELETE_BROWSING_DATA";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
     field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
@@ -439,7 +448,7 @@
     field public static final String MUTE_AUDIO = "MUTE_AUDIO";
     field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
     field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
-    field @SuppressCompatibility @androidx.webkit.Profile.ExperimentalUrlPrefetch public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V2";
+    field @SuppressCompatibility @androidx.webkit.Profile.ExperimentalUrlPrefetch public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V3";
     field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
     field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
     field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
@@ -460,6 +469,7 @@
     field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
     field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
     field public static final String SPECULATIVE_LOADING = "SPECULATIVE_LOADING_STATUS";
+    field public static final String STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES = "STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES";
     field public static final String STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX = "STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX";
     field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS";
     field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index 3fe8986..9e58425 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -59,17 +59,18 @@
     method public static void apply(androidx.webkit.ProcessGlobalConfig);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDataDirectorySuffix(android.content.Context, String);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDirectoryBasePaths(android.content.Context, java.io.File, java.io.File);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setPartitionedCookiesEnabled(android.content.Context, boolean);
   }
 
   public interface Profile {
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void clearPrefetchAsync(String, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void clearPrefetchAsync(String, java.util.concurrent.Executor, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.CookieManager getCookieManager();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public String getName();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.ServiceWorkerController getServiceWorkerController();
     method @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROFILE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public android.webkit.WebStorage getWebStorage();
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, androidx.core.os.CancellationSignal?, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
-    method @SuppressCompatibility @AnyThread @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, androidx.core.os.CancellationSignal?, androidx.webkit.SpeculativeLoadingParameters, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
+    method @SuppressCompatibility @RequiresFeature(name=androidx.webkit.WebViewFeature.PROFILE_URL_PREFETCH, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread @androidx.webkit.Profile.ExperimentalUrlPrefetch public void prefetchUrlAsync(String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.webkit.SpeculativeLoadingParameters, androidx.webkit.OutcomeReceiverCompat<java.lang.Void!,androidx.webkit.PrefetchException!>);
     field public static final String DEFAULT_PROFILE_NAME = "Default";
   }
 
@@ -333,6 +334,13 @@
   @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.TYPE}) public static @interface WebSettingsCompat.ExperimentalSpeculativeLoading {
   }
 
+  public final class WebStorageCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static void deleteBrowsingData(android.webkit.WebStorage, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static void deleteBrowsingData(android.webkit.WebStorage, java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static String deleteBrowsingDataForSite(android.webkit.WebStorage, String, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DELETE_BROWSING_DATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") @UiThread public static String deleteBrowsingDataForSite(android.webkit.WebStorage, String, java.util.concurrent.Executor, Runnable);
+  }
+
   public final class WebViewAssetLoader {
     method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
     field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
@@ -424,6 +432,7 @@
     field public static final String BACK_FORWARD_CACHE = "BACK_FORWARD_CACHE";
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DEFAULT_TRAFFICSTATS_TAGGING = "DEFAULT_TRAFFICSTATS_TAGGING";
+    field public static final String DELETE_BROWSING_DATA = "DELETE_BROWSING_DATA";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
     field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
@@ -439,7 +448,7 @@
     field public static final String MUTE_AUDIO = "MUTE_AUDIO";
     field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
     field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
-    field @SuppressCompatibility @androidx.webkit.Profile.ExperimentalUrlPrefetch public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V2";
+    field @SuppressCompatibility @androidx.webkit.Profile.ExperimentalUrlPrefetch public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V3";
     field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
     field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
     field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
@@ -460,6 +469,7 @@
     field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
     field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
     field public static final String SPECULATIVE_LOADING = "SPECULATIVE_LOADING_STATUS";
+    field public static final String STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES = "STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES";
     field public static final String STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX = "STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX";
     field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS";
     field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ProcessGlobalConfig.java b/webkit/webkit/src/main/java/androidx/webkit/ProcessGlobalConfig.java
index 6bb25fa..9bcbc95 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ProcessGlobalConfig.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ProcessGlobalConfig.java
@@ -20,7 +20,6 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.RequiresFeature;
-import androidx.annotation.RestrictTo;
 import androidx.webkit.internal.ApiHelperForP;
 import androidx.webkit.internal.StartupApiFeature;
 import androidx.webkit.internal.WebViewFeatureInternal;
@@ -197,7 +196,6 @@
      * Partitioned cookies will be enabled by default for apps that target Android B and above.
      * For apps that target below Android B, this is disabled.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresFeature(name = WebViewFeature.STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES,
             enforcement =
                     "androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)")
diff --git a/webkit/webkit/src/main/java/androidx/webkit/Profile.java b/webkit/webkit/src/main/java/androidx/webkit/Profile.java
index fd0e2e3..ec6fcb1 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/Profile.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/Profile.java
@@ -16,6 +16,7 @@
 
 package androidx.webkit;
 
+import android.os.CancellationSignal;
 import android.webkit.CookieManager;
 import android.webkit.GeolocationPermissions;
 import android.webkit.ServiceWorkerController;
@@ -24,7 +25,7 @@
 import androidx.annotation.AnyThread;
 import androidx.annotation.RequiresFeature;
 import androidx.annotation.RequiresOptIn;
-import androidx.core.os.CancellationSignal;
+import androidx.annotation.UiThread;
 
 import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
@@ -33,6 +34,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * A Profile represents one browsing session for WebView.
@@ -123,7 +126,7 @@
     }
 
     /**
-     * Starts a URL prefetch request. Can be called from any thread.
+     * Starts a URL prefetch request. Must be called from the UI thread.
      * <p>
      * All WebViews associated with this Profile will use a URL request
      * matching algorithm during execution of all variants of
@@ -137,32 +140,36 @@
      * {@link android.webkit.WebView#loadUrl(String)} to display web contents
      * in a WebView.
      * <p>
+     * NOTE: Additional headers passed to
+     * {@link android.webkit.WebView#loadUrl(String, Map)} are not considered
+     * in the matching algorithm for determining whether or not to serve a
+     * prefetched response to a navigation.
+     * <p>
      * For max latency saving benefits, it is recommended to call this method
      * as early as possible (i.e. before any WebView associated with this
      * profile is created).
      * <p>
      * Only supports HTTPS scheme.
-     * <p>
-     * All result callbacks will be resolved on the calling thread.
-     * <p>
      *
      * @param url                the url associated with the prefetch request.
      * @param cancellationSignal will make the best effort to cancel an
      *                           in-flight prefetch request, However cancellation is not
      *                           guaranteed.
+     * @param callbackExecutor   the executor to resolve the callback with.
      * @param operationCallback  callbacks for reporting result back to application.
      * @throws IllegalArgumentException if the url or callback is null.
      */
     @RequiresFeature(name = WebViewFeature.PROFILE_URL_PREFETCH,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
-    @AnyThread
+    @UiThread
     @ExperimentalUrlPrefetch
     void prefetchUrlAsync(@NonNull String url,
             @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor callbackExecutor,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> operationCallback);
 
     /**
-     * Starts a URL prefetch request. Can be called from any thread.
+     * Starts a URL prefetch request. Must be called from the UI thread.
      * <p>
      * All WebViews associated with this Profile will use a URL request
      * matching algorithm during execution of all variants of
@@ -176,29 +183,33 @@
      * {@link android.webkit.WebView#loadUrl(String)} to display web contents
      * in a WebView.
      * <p>
+     * NOTE: Additional headers passed to
+     * {@link android.webkit.WebView#loadUrl(String, Map)} are not considered
+     * in the matching algorithm for determining whether or not to serve a
+     * prefetched response to a navigation.
+     * <p>
      * For max latency saving benefits, it is recommended to call this method
      * as early as possible (i.e. before any WebView associated with this
      * profile is created).
      * <p>
      * Only supports HTTPS scheme.
-     * <p>
-     * All result callbacks will be resolved on the calling thread.
-     * <p>
      *
      * @param url                          the url associated with the prefetch request.
      * @param cancellationSignal           will make the best effort to cancel an
      *                                     in-flight prefetch request, However cancellation is not
      *                                     guaranteed.
+     * @param callbackExecutor             the executor to resolve the callback with.
      * @param speculativeLoadingParameters parameters to customize the prefetch request.
      * @param operationCallback            callbacks for reporting result back to application.
      * @throws IllegalArgumentException if the url or callback is null.
      */
     @RequiresFeature(name = WebViewFeature.PROFILE_URL_PREFETCH,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
-    @AnyThread
+    @UiThread
     @ExperimentalUrlPrefetch
     void prefetchUrlAsync(@NonNull String url,
             @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor callbackExecutor,
             @NonNull SpeculativeLoadingParameters speculativeLoadingParameters,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> operationCallback);
 
@@ -210,16 +221,19 @@
      * not be served to a WebView before it is cleared.
      * <p>
      *
-     * @param url               the url associated with the prefetch request.
+     * @param url               the url associated with the prefetch request. Should be
+     *                          an exact match with the URL passed to {@link #prefetchUrlAsync}.
+     * @param callbackExecutor  the executor to resolve the callback with.
      * @param operationCallback runs when the clear operation is complete Or and error occurred
      *                          during it.
      * @throws IllegalArgumentException if the url or callback is null.
      */
     @RequiresFeature(name = WebViewFeature.PROFILE_URL_PREFETCH,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
-    @AnyThread
+    @UiThread
     @ExperimentalUrlPrefetch
     void clearPrefetchAsync(@NonNull String url,
+            @NonNull Executor callbackExecutor,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> operationCallback);
 
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebStorageCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebStorageCompat.java
index d461610..2013bc0 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebStorageCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebStorageCompat.java
@@ -21,7 +21,6 @@
 import android.webkit.WebStorage;
 
 import androidx.annotation.RequiresFeature;
-import androidx.annotation.RestrictTo;
 import androidx.annotation.UiThread;
 import androidx.webkit.internal.ApiFeature;
 import androidx.webkit.internal.WebStorageAdapter;
@@ -44,10 +43,14 @@
  * {@link androidx.webkit.Profile#getWebStorage()} if your app is using multiple
  * WebView profiles.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public final class WebStorageCompat {
 
     /**
+     * Class is not intended to be instantiated.
+     */
+    private WebStorageCompat() {}
+
+    /**
      * Delete all data stored by websites in the given WebStorage instance.
      * This includes network cache, cookies, and any JavaScript-readable storage.
      * <p>
@@ -62,7 +65,6 @@
     @RequiresFeature(name = WebViewFeature.DELETE_BROWSING_DATA,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
     @UiThread
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static void deleteBrowsingData(
             @NonNull WebStorage instance, @NonNull Executor executor,
             @NonNull Runnable doneCallback) {
@@ -86,7 +88,6 @@
     @RequiresFeature(name = WebViewFeature.DELETE_BROWSING_DATA,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
     @UiThread
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static void deleteBrowsingData(
             @NonNull WebStorage instance, @NonNull Runnable doneCallback) {
         deleteBrowsingData(instance, r -> new Handler(Looper.getMainLooper()).post(r),
@@ -122,9 +123,7 @@
      */
     @RequiresFeature(name = WebViewFeature.DELETE_BROWSING_DATA,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
-
     @UiThread
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static @NonNull String deleteBrowsingDataForSite(
             @NonNull WebStorage instance, @NonNull String site, @NonNull Executor executor,
             @NonNull Runnable doneCallback) {
@@ -149,7 +148,6 @@
     @RequiresFeature(name = WebViewFeature.DELETE_BROWSING_DATA,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
     @UiThread
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static @NonNull String deleteBrowsingDataForSite(
             @NonNull WebStorage instance, @NonNull String site, @NonNull Runnable doneCallback) {
         return deleteBrowsingDataForSite(instance, site,
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index e6d322f..24e8e30 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -19,17 +19,18 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.Uri;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.webkit.CookieManager;
 import android.webkit.ValueCallback;
 import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebSettings;
+import android.webkit.WebStorage;
 import android.webkit.WebView;
 
 import androidx.annotation.RestrictTo;
 import androidx.annotation.StringDef;
-import androidx.core.os.CancellationSignal;
 import androidx.webkit.internal.WebViewFeatureInternal;
 
 import org.jspecify.annotations.NonNull;
@@ -49,9 +50,11 @@
  */
 public class WebViewFeature {
 
-    private WebViewFeature() {}
+    private WebViewFeature() {
+    }
 
     /**
+     *
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @StringDef(value = {
@@ -118,9 +121,11 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.PARAMETER, ElementType.METHOD})
-    public @interface WebViewSupportFeature {}
+    public @interface WebViewSupportFeature {
+    }
 
     /**
+     *
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @StringDef(value = {
@@ -130,7 +135,8 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.PARAMETER, ElementType.METHOD})
-    public @interface WebViewStartupFeature {}
+    public @interface WebViewStartupFeature {
+    }
 
     /**
      * Feature for {@link #isFeatureSupported(String)}.
@@ -138,7 +144,7 @@
      * {@link androidx.webkit.WebViewCompat#postVisualStateCallback(android.webkit.WebView, long,
      * WebViewCompat.VisualStateCallback)}, and {@link
      * WebViewClientCompat#onPageCommitVisible(
-     * android.webkit.WebView, String)}.
+     *android.webkit.WebView, String)}.
      */
     public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
 
@@ -373,7 +379,7 @@
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers
      * {@link androidx.webkit.WebMessagePortCompat#setWebMessageCallback(
-     * WebMessagePortCompat.WebMessageCallbackCompat)}, and
+     *WebMessagePortCompat.WebMessageCallbackCompat)}, and
      * {@link androidx.webkit.WebMessagePortCompat#setWebMessageCallback(Handler,
      * WebMessagePortCompat.WebMessageCallbackCompat)}.
      */
@@ -428,7 +434,7 @@
     public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
 
     /**
-     i* Feature for {@link #isFeatureSupported(String)}.
+     * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers
      * {@link androidx.webkit.WebViewCompat#getWebViewRenderProcessClient(WebView)},
      * {@link androidx.webkit.WebViewCompat#setWebViewRenderProcessClient(WebView, WebViewRenderProcessClient)},
@@ -507,7 +513,6 @@
      * This feature covers
      * {@link WebSettingsCompat#setEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings, boolean)}and
      * {@link WebSettingsCompat#getEnterpriseAuthenticationAppLinkPolicyEnabled(WebSettings)}.
-     *
      */
     public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
             "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
@@ -540,17 +545,16 @@
      * This feature covers
      * {@link androidx.webkit.ProcessGlobalConfig#setDirectoryBasePaths(Context, File, File)}
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES =
             "STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES";
 
     /**
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers
-     * {@link androidx.webkit.WebSettingsCompat#getRequestedWithHeaderOriginAllowList(WebSettings)],
-     * {@link androidx.webkit.WebSettingsCompat#setRequestedWithHeaderAllowList(WebSettings, Set)},
-     * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#getRequestedWithHeaderAllowList(WebSettings)}, and
-     * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setRequestedWithHeaderAllowList(WebSettings, Set)}.
+     * {@link androidx.webkit.WebSettingsCompat#getRequestedWithHeaderOriginAllowList(WebSettings)},
+     * {@link androidx.webkit.WebSettingsCompat#setRequestedWithHeaderOriginAllowList(WebSettings, Set)},
+     * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#getRequestedWithHeaderOriginAllowList()},
+     * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setRequestedWithHeaderOriginAllowList(Set)}
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String REQUESTED_WITH_HEADER_ALLOW_LIST =
@@ -634,12 +638,12 @@
     /**
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers
-     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, SpeculativeLoadingParameters, OutcomeReceiverCompat)}
-     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, OutcomeReceiverCompat)}
-     * {@link androidx.webkit.Profile#clearPrefetchAsync(String, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, android.os.CancellationSignal, Executor, SpeculativeLoadingParameters, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, Executor, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#clearPrefetchAsync(String, Executor, OutcomeReceiverCompat)}
      */
     @Profile.ExperimentalUrlPrefetch
-    public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V2";
+    public static final String PROFILE_URL_PREFETCH = "PREFETCH_URL_V3";
 
     /**
      * Feature for {@link #isFeatureSupported(String)}.
@@ -648,7 +652,14 @@
      */
     public static final String DEFAULT_TRAFFICSTATS_TAGGING = "DEFAULT_TRAFFICSTATS_TAGGING";
 
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * This feature covers
+     * {@link androidx.webkit.WebStorageCompat#deleteBrowsingData(WebStorage, Executor, Runnable)},
+     * {@link androidx.webkit.WebStorageCompat#deleteBrowsingData(WebStorage, Runnable)},
+     * {@link androidx.webkit.WebStorageCompat#deleteBrowsingDataForSite(WebStorage, String, Executor, Runnable)},
+     * {@link androidx.webkit.WebStorageCompat#deleteBrowsingDataForSite(WebStorage, String, Runnable)}
+     */
     public static final String DELETE_BROWSING_DATA = "DELETE_BROWSING_DATA";
 
     /**
@@ -657,9 +668,9 @@
      * device, and the WebView APK on the device.
      *
      * <p class="note"><b>Note:</b> This method is different from
-     * {@link WebViewFeature#isStartupFeatureSupported(Context, String)} and this method only accepts
-     * certain features. Please verify that the correct feature checking method is used for a
-     * particular feature.
+     * {@link WebViewFeature#isStartupFeatureSupported(Context, String)} and this method only
+     * accepts certain features. Please verify that the correct feature checking method is used for
+     * a particular feature.
      *
      * <p class="note"><b>Note:</b> If this method returns {@code false}, it is not safe to invoke
      * the methods requiring the desired feature. Furthermore, if this method returns {@code false}
@@ -686,7 +697,7 @@
      * the methods requiring the desired feature. Furthermore, if this method returns {@code false}
      * for a particular feature, any callback guarded by that feature will not be invoked.
      *
-     * @param context a Context to access application assets This value cannot be null.
+     * @param context        a Context to access application assets This value cannot be null.
      * @param startupFeature the startup feature to be checked
      * @return whether the feature is supported given the current platform SDK and WebView version
      */
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ProfileImpl.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ProfileImpl.java
index 6b84532..dd61055 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ProfileImpl.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ProfileImpl.java
@@ -16,21 +16,25 @@
 
 package androidx.webkit.internal;
 
+import android.os.CancellationSignal;
 import android.webkit.CookieManager;
 import android.webkit.GeolocationPermissions;
 import android.webkit.ServiceWorkerController;
 import android.webkit.WebStorage;
 
-import androidx.core.os.CancellationSignal;
 import androidx.webkit.OutcomeReceiverCompat;
 import androidx.webkit.PrefetchException;
 import androidx.webkit.Profile;
 import androidx.webkit.SpeculativeLoadingParameters;
 
 import org.chromium.support_lib_boundary.ProfileBoundaryInterface;
+import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
 import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
 
+import java.lang.reflect.InvocationHandler;
+import java.util.concurrent.Executor;
+
 
 /**
  * Internal implementation of Profile.
@@ -103,19 +107,49 @@
     @Override
     public void prefetchUrlAsync(@NonNull String url,
             @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor callbackExecutor,
             @NonNull SpeculativeLoadingParameters params,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> callback) {
+        ApiFeature.NoFramework feature = WebViewFeatureInternal.PROFILE_URL_PREFETCH;
+        if (feature.isSupportedByWebView()) {
+            InvocationHandler paramsBoundaryInterface =
+                    BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(
+                            new SpeculativeLoadingParametersAdapter(params));
+
+            mProfileImpl.prefetchUrl(url, cancellationSignal, callbackExecutor,
+                    paramsBoundaryInterface,
+                    PrefetchOperationCallbackAdapter.buildInvocationHandler(callback));
+
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
     }
 
     @Override
     public void prefetchUrlAsync(@NonNull String url,
             @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor callbackExecutor,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> callback) {
+        ApiFeature.NoFramework feature = WebViewFeatureInternal.PROFILE_URL_PREFETCH;
+        if (feature.isSupportedByWebView()) {
+            mProfileImpl.prefetchUrl(url, cancellationSignal, callbackExecutor,
+                    PrefetchOperationCallbackAdapter.buildInvocationHandler(callback));
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
     }
 
     @Override
     public void clearPrefetchAsync(@NonNull String url,
+            @NonNull Executor callbackExecutor,
             @NonNull OutcomeReceiverCompat<Void, PrefetchException> callback) {
+        ApiFeature.NoFramework feature = WebViewFeatureInternal.PROFILE_URL_PREFETCH;
+        if (feature.isSupportedByWebView()) {
+            mProfileImpl.clearPrefetch(url, callbackExecutor,
+                    PrefetchOperationCallbackAdapter.buildInvocationHandler(callback));
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
     }
 
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/StartupFeatures.java b/webkit/webkit/src/main/java/androidx/webkit/internal/StartupFeatures.java
index a847af2..54d505c 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/StartupFeatures.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/StartupFeatures.java
@@ -16,8 +16,6 @@
 
 package androidx.webkit.internal;
 
-import androidx.annotation.RestrictTo;
-
 /**
  * Class containing all the startup features the AndroidX library can support.
  * The constants defined in this file should not be modified as their value is used in the
@@ -37,7 +35,6 @@
             "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH";
 
     // ProcessGlobalConfig#setPartitionedCookiesEnabled(boolean)
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public static final String STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES =
             "STARTUP_FEATURE_CONFIGURE_PARTITIONED_COOKIES";
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index e61807c..efde9bc 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageInfo;
 import android.net.Uri;
 import android.os.Build;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.webkit.ValueCallback;
 import android.webkit.WebResourceRequest;
@@ -29,7 +30,6 @@
 
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
-import androidx.core.os.CancellationSignal;
 import androidx.webkit.OutcomeReceiverCompat;
 import androidx.webkit.Profile;
 import androidx.webkit.ProfileStore;
@@ -650,8 +650,9 @@
     /**
      * Feature for {@link WebViewFeature#isFeatureSupported(String)}.
      * This feature covers
-     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, SpeculativeLoadingParameters, OutcomeReceiverCompat)}
-     * {@link androidx.webkit.Profile#clearPrefetchAsync(String, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, Executor, SpeculativeLoadingParameters, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#prefetchUrlAsync(String, CancellationSignal, Executor, OutcomeReceiverCompat)}
+     * {@link androidx.webkit.Profile#clearPrefetchAsync(String, Executor, OutcomeReceiverCompat)}
      */
     public static final ApiFeature.NoFramework PROFILE_URL_PREFETCH =
             new ApiFeature.NoFramework(WebViewFeature.PROFILE_URL_PREFETCH,