[go: nahoru, domu]

Merge "Revert "Revert "Changes CacheDrawModifierNode to sealed interface and makes other improvements.""" into androidx-main
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
index 8fdbdc0..62ca811 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
@@ -50,17 +50,19 @@
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession
             >(ACTION_SPEC) {
 
-        private var properties = mutableMapOf<String, Property<*>>()
+        fun setCallFormatProperty(
+            callFormat: Property<Call.CanonicalValue.CallFormat>
+        ): CapabilityBuilder =
+            setProperty(
+                PropertyMapStrings.CALL_FORMAT.key,
+                callFormat,
+                TypeConverters.CALL_FORMAT_ENTITY_CONVERTER)
 
-        fun setCallFormat(callFormat: Property<Call.CanonicalValue.CallFormat>): CapabilityBuilder =
-            apply {
-                properties[PropertyMapStrings.CALL_FORMAT.key] = callFormat
-            }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setParticipantProperty(participant: Property<Participant>): CapabilityBuilder =
+            setProperty(
+            PropertyMapStrings.PARTICIPANT.key,
+            participant,
+            EntityConverter.of(PARTICIPANT_TYPE_SPEC))
     }
 
     class Arguments
@@ -177,29 +179,19 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "call.callFormat",
-                    { properties ->
-                        properties[PropertyMapStrings.CALL_FORMAT.key]
-                            as? Property<Call.CanonicalValue.CallFormat>
-                    },
                     Arguments.Builder::setCallFormat,
-                    TypeConverters.CALL_FORMAT_PARAM_VALUE_CONVERTER,
-                    TypeConverters.CALL_FORMAT_ENTITY_CONVERTER
+                    TypeConverters.CALL_FORMAT_PARAM_VALUE_CONVERTER
                 )
                 .bindRepeatedParameter(
                     "call.participant",
-                    { properties ->
-                        properties[PropertyMapStrings.PARTICIPANT.key] as? Property<Participant>
-                    },
                     Arguments.Builder::setParticipantList,
                     ParticipantValue.PARAM_VALUE_CONVERTER,
-                    EntityConverter.of(PARTICIPANT_TYPE_SPEC)
                 )
                 .bindOutput(
                     "call",
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
index b559a71..78af5b2 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
@@ -50,21 +50,19 @@
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession
             >(ACTION_SPEC) {
+        fun setMessageTextProperty(
+            messageText: Property<StringValue>
+        ): CapabilityBuilder = setProperty(
+            PropertyMapStrings.MESSAGE_TEXT.key,
+            messageText,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
 
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setMessageText(messageText: Property<StringValue>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.MESSAGE_TEXT.key] = messageText
-        }
-
-        fun setRecipient(recipient: Property<Recipient>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.RECIPIENT.key] = recipient
-        }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setRecipientProperty(recipient: Property<Recipient>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.RECIPIENT.key,
+            recipient,
+            EntityConverter.of(TypeConverters.RECIPIENT_TYPE_SPEC)
+        )
     }
 
     class Arguments
@@ -179,28 +177,19 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     "message.recipient",
-                    { properties ->
-                        properties[PropertyMapStrings.RECIPIENT.key] as? Property<Recipient>
-                    },
                     Arguments.Builder::setRecipientList,
                     RecipientValue.PARAM_VALUE_CONVERTER,
-                    EntityConverter.of(RECIPIENT_TYPE_SPEC)
                 )
                 .bindParameter(
                     "message.text",
-                    { properties ->
-                        properties[PropertyMapStrings.MESSAGE_TEXT.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setMessageText,
                     TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOutput(
                     "message",
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/Capability.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/Capability.kt
index 0fcd4a2..1e1be97 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/Capability.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/Capability.kt
@@ -20,7 +20,9 @@
 import androidx.annotation.RestrictTo
 import androidx.appactions.interaction.capabilities.core.impl.CapabilitySession
 import androidx.appactions.interaction.capabilities.core.impl.SingleTurnCapabilityImpl
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
+import androidx.appactions.interaction.capabilities.core.impl.spec.BoundProperty
 import androidx.appactions.interaction.capabilities.core.impl.task.EmptyTaskUpdater
 import androidx.appactions.interaction.capabilities.core.impl.task.SessionBridge
 import androidx.appactions.interaction.capabilities.core.impl.task.TaskCapabilityImpl
@@ -71,7 +73,7 @@
         ExecutionSessionT : BaseExecutionSession<ArgumentsT, OutputT>
         > private constructor() {
         private var id: String? = null
-        private var property: Map<String, Property<*>>? = null
+        private val boundPropertyMap = mutableMapOf<String, BoundProperty<*>>()
         private var executionCallback: ExecutionCallback<ArgumentsT, OutputT>? = null
         private var sessionFactory:
             (hostProperties: HostProperties?) -> ExecutionSessionT? = { _ -> null }
@@ -107,11 +109,14 @@
         }
 
         /**
-         * Sets the Property instance for this capability.
-         */
+         * Sets a single slot property for this capability, and associated entity converter. */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        fun setProperty(property: Map<String, Property<*>>) = asBuilder().apply {
-            this.property = property
+        protected fun <T> setProperty(
+            slotName: String,
+            property: Property<T>,
+            entityConverter: EntityConverter<T>,
+        ) = asBuilder().apply {
+            this.boundPropertyMap[slotName] = BoundProperty(slotName, property, entityConverter)
         }
 
         /**
@@ -162,19 +167,19 @@
         /** Builds and returns this Capability. */
         open fun build(): Capability {
             val checkedId = requireNotNull(id) { "setId must be called before build" }
-            val checkedProperty = requireNotNull(property) { "property must not be null." }
+            val boundProperties = boundPropertyMap.values.toList()
             if (executionCallback != null) {
                 return SingleTurnCapabilityImpl(
                     checkedId,
                     actionSpec!!,
-                    checkedProperty,
+                    boundProperties,
                     executionCallback!!
                 )
             } else {
                 return TaskCapabilityImpl(
                     checkedId,
                     actionSpec!!,
-                    checkedProperty,
+                    boundProperties,
                     sessionFactory,
                     sessionBridge!!,
                     ::EmptyTaskUpdater
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
index 862385b..a939b03 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
@@ -21,9 +21,8 @@
 import androidx.appactions.interaction.capabilities.core.ExecutionCallback
 import androidx.appactions.interaction.capabilities.core.HostProperties
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
-import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.core.impl.spec.BoundProperty
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
-import androidx.appactions.interaction.proto.TaskInfo
 import kotlinx.coroutines.sync.Mutex
 
 @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -33,16 +32,16 @@
     > constructor(
     id: String,
     val actionSpec: ActionSpec<ArgumentsT, OutputT>,
-    val property: Map<String, Property<*>>,
+    val boundProperties: List<BoundProperty<*>>,
     val executionCallback: ExecutionCallback<ArgumentsT, OutputT>,
 ) : Capability(id) {
     private val mutex = Mutex()
 
-    override val appAction: AppAction get() =
-        actionSpec.convertPropertyToProto(property).toBuilder()
-            .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(false))
-            .setIdentifier(id)
-            .build()
+    override val appAction: AppAction get() = actionSpec.createAppAction(
+        id,
+        boundProperties,
+        supportsPartialFulfillment = false
+    )
 
     override fun createSession(
         sessionId: String,
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.kt
index 450d3d3..1cc28f8 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.kt
@@ -17,7 +17,6 @@
 package androidx.appactions.interaction.capabilities.core.impl.spec
 
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
-import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.proto.AppActionsContext
 import androidx.appactions.interaction.proto.FulfillmentResponse
 import androidx.appactions.interaction.proto.ParamValue
@@ -30,8 +29,18 @@
  */
 interface ActionSpec<ArgumentsT, OutputT> {
 
-    /** Converts the property to the `AppAction` proto.  */
-    fun convertPropertyToProto(property: Map<String, Property<*>>): AppActionsContext.AppAction
+    /**
+     * Converts the input parameters to the `AppAction` proto.
+     * @param identifier                    the capability identifier
+     * @param boundProperties               the list of BoundProperty instances.
+     * @param supportsPartialFulfillment    whether or not this capability supports partial
+     * fulfillment.
+     */
+    fun createAppAction(
+        identifier: String,
+        boundProperties: List<BoundProperty<*>>,
+        supportsPartialFulfillment: Boolean
+    ): AppActionsContext.AppAction
 
     /** Builds this action's arguments from an ArgumentsWrapper instance.  */
     @Throws(StructConversionException::class)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
index ad4b7b6..51217f6 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
@@ -17,14 +17,10 @@
 package androidx.appactions.interaction.capabilities.core.impl.spec
 
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
-import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.SlotTypeConverter
 import androidx.appactions.interaction.capabilities.core.impl.spec.ParamBinding.ArgumentSetter
-import androidx.appactions.interaction.capabilities.core.impl.spec.ParamBinding.Companion.create
 import androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors
-import androidx.appactions.interaction.capabilities.core.properties.Property
-import androidx.appactions.interaction.proto.AppActionsContext
 import androidx.appactions.interaction.proto.ParamValue
 import java.util.function.BiConsumer
 import java.util.function.Function
@@ -69,41 +65,30 @@
      */
     private fun bindParameterInternal(
         paramName: String,
-        paramGetter: Function<Map<String, Property<*>>, AppActionsContext.IntentParameter?>,
         argumentSetter: ArgumentSetter<ArgumentsBuilderT>
     ): ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT, OutputT> {
-        paramBindingList.add(create(paramName, paramGetter, argumentSetter))
+        paramBindingList.add(ParamBinding(paramName, argumentSetter))
         return this
     }
 
     /**
-     * Binds the parameter name, getter, and setter for a [Property].
+     * Binds the parameter name, and corresponding method references for setting Argument value.
      *
      * If the Property getter returns a null value, this parameter will not exist in the parameter
      * definition of the capability.
      *
      * @param paramName the name of this action' parameter.
-     * @param propertyGetter a getter of the Property from the property, which must be able to
-     * fetch a non-null `Property` from `PropertyT`.
      * @param paramConsumer a setter to set the string value in the argument builder.
      * @param paramValueConverter converter FROM assistant ParamValue proto
-     * @param entityConverter converter TO assistant Entity proto
      * @return the builder itself.
      */
-    fun <T, PossibleValueT> bindParameter(
+    fun <T> bindParameter(
         paramName: String,
-        propertyGetter: Function<Map<String, Property<*>>, Property<PossibleValueT>?>,
         paramConsumer: BiConsumer<in ArgumentsBuilderT, T>,
         paramValueConverter: ParamValueConverter<T>,
-        entityConverter: EntityConverter<PossibleValueT>
     ): ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT, OutputT> {
         return bindParameterInternal(
             paramName,
-            { propertyMap ->
-                propertyGetter.apply(propertyMap)?.let {
-                    buildIntentParameter(paramName, it, entityConverter)
-                }
-            },
             { argBuilder: ArgumentsBuilderT, paramList: List<ParamValue> ->
                 if (paramList.isNotEmpty()) {
                     paramConsumer.accept(
@@ -122,20 +107,13 @@
      * If the Property getter returns a null value, this parameter will not exist in the parameter
      * definition of the capability.
      */
-    fun <T, PossibleValueT> bindRepeatedParameter(
+    fun <T> bindRepeatedParameter(
         paramName: String,
-        propertyGetter: Function<Map<String, Property<*>>, Property<PossibleValueT>?>,
         paramConsumer: BiConsumer<in ArgumentsBuilderT, List<T>>,
-        paramValueConverter: ParamValueConverter<T>,
-        entityConverter: EntityConverter<PossibleValueT>
+        paramValueConverter: ParamValueConverter<T>
     ): ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT, OutputT> {
         return bindParameterInternal(
             paramName,
-            { propertyMap ->
-                propertyGetter.apply(propertyMap)?.let {
-                    buildIntentParameter(paramName, it, entityConverter)
-                }
-            },
             { argBuilder: ArgumentsBuilderT, paramList: List<ParamValue?>? ->
                 paramConsumer.accept(
                     argBuilder,
@@ -209,26 +187,5 @@
         ): ActionSpecBuilder<Any, BuilderOf<Any>, Any> {
             return ActionSpecBuilder(capabilityName) { BuilderOf { Object() } }
         }
-
-        /** Create IntentParameter proto from a Property.  */
-        internal fun <T> buildIntentParameter(
-            paramName: String,
-            property: Property<T>,
-            entityConverter: EntityConverter<T>
-        ): AppActionsContext.IntentParameter {
-            val builder = AppActionsContext.IntentParameter.newBuilder()
-                .setName(paramName)
-                .setIsRequired(property.isRequired)
-                .setEntityMatchRequired(property.isValueMatchRequired)
-                .setIsProhibited(property.isProhibited)
-            property.possibleValues.stream()
-                .map { possibleValue ->
-                    entityConverter.convert(possibleValue)
-                }
-                .forEach { entityProto ->
-                    builder.addPossibleEntities(entityProto)
-                }
-            return builder.build()
-        }
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
index a05c8ed..1c59144 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
@@ -18,11 +18,10 @@
 
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
-import androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors
-import androidx.appactions.interaction.capabilities.core.properties.Property
-import androidx.appactions.interaction.proto.AppActionsContext
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
 import androidx.appactions.interaction.proto.FulfillmentResponse
 import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TaskInfo
 import java.util.function.Function
 import java.util.function.Supplier
 
@@ -33,20 +32,20 @@
     private val paramBindingList: List<ParamBinding<ArgumentsT, ArgumentsBuilderT>>,
     private val outputBindings: Map<String, Function<OutputT, List<ParamValue>>>
 ) : ActionSpec<ArgumentsT, OutputT> {
-
-    override fun convertPropertyToProto(
-        property: Map<String, Property<*>>
-    ): AppActionsContext.AppAction {
-        return AppActionsContext.AppAction.newBuilder()
+    override fun createAppAction(
+        identifier: String,
+        boundProperties: List<BoundProperty<*>>,
+        supportsPartialFulfillment: Boolean
+    ): AppAction = AppAction.newBuilder()
             .setName(capabilityName)
+            .setIdentifier(identifier)
             .addAllParams(
-                paramBindingList.stream()
-                    .map { binding -> binding.propertyConverter.apply(property) }
-                    .filter { intentParam -> intentParam != null }
-                    .collect(ImmutableCollectors.toImmutableList())
+                boundProperties.map(BoundProperty<*>::convertToProto)
+            )
+            .setTaskInfo(
+                TaskInfo.newBuilder().setSupportsPartialFulfillment(supportsPartialFulfillment)
             )
             .build()
-    }
 
     @Throws(StructConversionException::class)
     override fun buildArguments(args: Map<String, List<ParamValue>>): ArgumentsT {
@@ -81,4 +80,4 @@
         }
         return outputBuilder.build()
     }
-}
\ No newline at end of file
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/BoundProperty.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/BoundProperty.kt
new file mode 100644
index 0000000..c0792f0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/BoundProperty.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.spec
+
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.utils.invokeExternalBlock
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
+
+/** Wrapper around [Property] and a type-specific EntityConverter. */
+data class BoundProperty<T> internal constructor(
+    val slotName: String,
+    val property: Property<T>,
+    val entityConverter: EntityConverter<T>
+) {
+    /** * Convert this wrapped Property into [IntentParameter] proto representation with
+     * current inventory.
+     */
+    fun convertToProto(): IntentParameter {
+        val builder = IntentParameter.newBuilder()
+            .setName(slotName)
+            .setIsRequired(property.isRequired)
+            .setEntityMatchRequired(property.isValueMatchRequired)
+            .setIsProhibited(property.isProhibited)
+        invokeExternalBlock("retrieving possibleValues for $slotName property") {
+            property.possibleValues
+        }.map {
+            entityConverter.convert(it)
+        }.forEach {
+            builder.addPossibleEntities(it)
+        }
+        return builder.build()
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
index 7e57ead..c8cd221 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
@@ -18,15 +18,11 @@
 
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
-import androidx.appactions.interaction.capabilities.core.properties.Property
-import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
 import androidx.appactions.interaction.proto.ParamValue
-import java.util.function.Function
 
 data class ParamBinding<ArgumentsT, ArgumentsBuilderT : BuilderOf<ArgumentsT>>
 internal constructor(
     val name: String,
-    val propertyConverter: Function<Map<String, Property<*>>, IntentParameter?>,
     val argumentSetter: ArgumentSetter<ArgumentsBuilderT>
 ) {
     /**
@@ -38,15 +34,4 @@
         @Throws(StructConversionException::class)
         fun setArguments(builder: ArgumentsBuilderT, paramValues: List<ParamValue>)
     }
-
-    companion object {
-        @JvmStatic
-        fun <ArgumentsT, ArgumentsBuilderT : BuilderOf<ArgumentsT>> create(
-            name: String,
-            paramGetter: Function<Map<String, Property<*>>, IntentParameter?>,
-            argumentSetter: ArgumentSetter<ArgumentsBuilderT>
-        ): ParamBinding<ArgumentsT, ArgumentsBuilderT> {
-            return ParamBinding(name, paramGetter, argumentSetter)
-        }
-    }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImpl.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImpl.kt
index 4dee5fa..9f21ab3 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImpl.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImpl.kt
@@ -21,9 +21,8 @@
 import androidx.appactions.interaction.capabilities.core.HostProperties
 import androidx.appactions.interaction.capabilities.core.impl.CapabilitySession
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
-import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.core.impl.spec.BoundProperty
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
-import androidx.appactions.interaction.proto.TaskInfo
 import java.util.function.Supplier
 
 /**
@@ -44,19 +43,17 @@
 constructor(
     id: String,
     private val actionSpec: ActionSpec<ArgumentsT, OutputT>,
-    private val property: Map<String, Property<*>>,
+    private val boundProperties: List<BoundProperty<*>>,
     private val sessionFactory: (hostProperties: HostProperties?) -> ExecutionSessionT?,
     private val sessionBridge: SessionBridge<ExecutionSessionT, ArgumentsT, ConfirmationT>,
     private val sessionUpdaterSupplier: Supplier<SessionUpdaterT>
 ) : Capability(id) {
 
-    override val appAction: AppAction get() =
-        actionSpec
-            .convertPropertyToProto(property)
-            .toBuilder()
-            .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
-            .setIdentifier(id)
-            .build()
+    override val appAction: AppAction get() = actionSpec.createAppAction(
+        id,
+        boundProperties,
+        supportsPartialFulfillment = true
+    )
 
     override fun createSession(
         sessionId: String,
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
index 322f1d7..44b5487 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
@@ -25,6 +25,7 @@
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.impl.spec.BoundProperty
 import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import androidx.appactions.interaction.capabilities.core.testing.spec.Arguments
@@ -50,7 +51,6 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@Suppress("UNCHECKED_CAST")
 class SingleTurnCapabilityTest {
     private val hostProperties =
         HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
@@ -59,15 +59,19 @@
     @Test
     fun appAction_computedProperty() {
         val mutableEntityList = mutableListOf<StringValue>()
+        val dynamicStringProperty = Property.Builder<StringValue>()
+            .setPossibleValueSupplier(
+                mutableEntityList::toList
+            ).build()
         val capability = SingleTurnCapabilityImpl(
             id = "capabilityId",
             actionSpec = ACTION_SPEC,
-            property = mutableMapOf(
-                "requiredEntity" to Property
-                    .Builder<StringValue>()
-                    .setPossibleValueSupplier(
-                        mutableEntityList::toList
-                    ).build()
+            boundProperties = listOf(
+                BoundProperty(
+                    "requiredString",
+                    dynamicStringProperty,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
             ),
             executionCallback = ExecutionCallback<Arguments, Output> {
                 ExecutionResult.Builder<Output>().build()
@@ -126,17 +130,22 @@
                     )
                     .build()
             }
-        val property = mutableMapOf<String, Property<*>>()
-        property.put(
-            "requiredString",
-            Property.Builder<StringValue>().build()
-        )
-        property.put("optionalString", Property.prohibited<StringValue>())
         val capability =
             SingleTurnCapabilityImpl(
                 id = "capabilityId",
                 actionSpec = ACTION_SPEC,
-                property = property,
+                boundProperties = listOf(
+                    BoundProperty(
+                        "requiredString",
+                        Property.Builder<StringValue>().build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    ),
+                    BoundProperty(
+                        "optionalString",
+                        Property.prohibited<StringValue>(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    )
+                ),
                 executionCallback = executionCallback
             )
 
@@ -184,17 +193,22 @@
     fun oneShotCapability_exceptionInExecutionCallback() {
         val executionCallback =
             ExecutionCallback<Arguments, Output> { throw IllegalStateException("") }
-        val property = mutableMapOf<String, Property<*>>()
-        property.put(
-            "requiredString",
-            Property.Builder<StringValue>().build()
-        )
-        property.put("optionalString", Property.prohibited<StringValue>())
         val capability =
             SingleTurnCapabilityImpl(
                 id = "capabilityId",
                 actionSpec = ACTION_SPEC,
-                property = property,
+                boundProperties = listOf(
+                    BoundProperty(
+                        "requiredString",
+                        Property.Builder<StringValue>().build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    ),
+                    BoundProperty(
+                        "optionalString",
+                        Property.prohibited<StringValue>(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    )
+                ),
                 executionCallback = executionCallback
             )
 
@@ -219,16 +233,17 @@
     fun oneShotSession_uiHandle_withExecutionCallback() {
         val executionCallback =
             ExecutionCallback<Arguments, Output> { ExecutionResult.Builder<Output>().build() }
-        val property = mutableMapOf<String, Property<*>>()
-        property.put(
-            "requiredString",
-            Property.Builder<StringValue>().build()
-        )
         val capability =
             SingleTurnCapabilityImpl(
                 id = "capabilityId",
                 actionSpec = ACTION_SPEC,
-                property = property,
+                boundProperties = listOf(
+                    BoundProperty(
+                        "requiredString",
+                        Property.Builder<StringValue>().build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    ),
+                ),
                 executionCallback = executionCallback
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
@@ -241,16 +256,17 @@
             ExecutionCallbackAsync<Arguments, Output> {
                 Futures.immediateFuture(ExecutionResult.Builder<Output>().build())
             }
-        val property = mutableMapOf<String, Property<*>>()
-        property.put(
-            "requiredString",
-            Property.Builder<StringValue>().build()
-        )
         val capability =
             SingleTurnCapabilityImpl(
                 id = "capabilityId",
                 actionSpec = ACTION_SPEC,
-                property = property,
+                boundProperties = listOf(
+                    BoundProperty(
+                        "requiredString",
+                        Property.Builder<StringValue>().build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    )
+                ),
                 executionCallback = executionCallbackAsync.toExecutionCallback()
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
@@ -266,15 +282,16 @@
             argumentChannel.send(it)
             executionResultChannel.receive()
         }
-        val property = mutableMapOf<String, Property<*>>()
-        property.put(
-            "requiredString",
-            Property.Builder<StringValue>().build()
-        )
         val capability = SingleTurnCapabilityImpl(
             id = "capabilityId",
             actionSpec = ACTION_SPEC,
-            property = property,
+            boundProperties = listOf(
+                BoundProperty(
+                    "requiredString",
+                    Property.Builder<StringValue>().build(),
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+            ),
             executionCallback = executionCallback
         )
         val session1 = capability.createSession("session1", hostProperties)
@@ -331,19 +348,13 @@
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "requiredString",
-                    { properties -> properties["requiredEntity"] as Property<StringValue> },
                     Arguments.Builder::setRequiredStringField,
                     TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindParameter(
                     "optionalString",
-                    { properties ->
-                        properties["optionalString"] as? Property<StringValue>
-                    },
                     Arguments.Builder::setOptionalStringField,
                     TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOutput(
                     "optionalStringOutput",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
index 899825f..23efab9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
@@ -33,6 +33,7 @@
 import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
 import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput.OutputValue;
 import androidx.appactions.interaction.proto.ParamValue;
+import androidx.appactions.interaction.proto.TaskInfo;
 import androidx.appactions.interaction.protobuf.Struct;
 import androidx.appactions.interaction.protobuf.Value;
 
@@ -40,10 +41,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-
 
 @RunWith(JUnit4.class)
 @SuppressWarnings("unchecked")
@@ -54,34 +53,16 @@
                     .setOutput(Output.class)
                     .bindParameter(
                             "requiredString",
-                            properties ->
-                            {
-                                Property<?> property = properties.get("requiredString");
-                                return (property == null) ? null : (Property<StringValue>) property;
-                            },
                             Arguments.Builder::setRequiredStringField,
-                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                            TypeConverters.STRING_VALUE_ENTITY_CONVERTER)
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER)
                     .bindParameter(
                             "optionalString",
-                            properties ->
-                            {
-                                Property<?> property = properties.get("optionalString");
-                                return (property == null) ? null : (Property<StringValue>) property;
-                            },
                             Arguments.Builder::setOptionalStringField,
-                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                            TypeConverters.STRING_VALUE_ENTITY_CONVERTER)
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER)
                     .bindRepeatedParameter(
                             "repeatedString",
-                            properties ->
-                            {
-                                Property<?> property = properties.get("repeatedString");
-                                return (property == null) ? null : (Property<StringValue>) property;
-                            },
                             Arguments.Builder::setRepeatedStringField,
-                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                            TypeConverters.STRING_VALUE_ENTITY_CONVERTER)
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER)
                     .bindOutput(
                             "optionalStringOutput",
                             Output::getOptionalStringField,
@@ -128,62 +109,65 @@
                     .setOutput(Output.class)
                     .bindParameter(
                             "requiredEntity",
-                            properties -> {
-                                Property<?> property = properties.get("requiredEntity");
-                                return (property == null) ? null : (Property<TestEntity>) property;
-                            },
                             GenericEntityArguments.Builder::setSingularField,
-                            TEST_ENTITY_PARAM_VALUE_CONVERTER,
-                            TEST_ENTITY_CONVERTER)
+                            TEST_ENTITY_PARAM_VALUE_CONVERTER)
                     .bindParameter(
                             "optionalEntity",
-                            properties -> {
-                                Property<?> property = properties.get("optionalEntity");
-                                return (property == null) ? null : (Property<TestEntity>) property;
-                            },
                             GenericEntityArguments.Builder::setOptionalField,
-                            TEST_ENTITY_PARAM_VALUE_CONVERTER,
-                            TEST_ENTITY_CONVERTER)
+                            TEST_ENTITY_PARAM_VALUE_CONVERTER)
                     .bindRepeatedParameter(
                             "repeatedEntities",
-                            properties -> {
-                                Property<?> property = properties.get("repeatedEntities");
-                                return (property == null) ? null : (Property<TestEntity>) property;
-                            },
                             GenericEntityArguments.Builder::setRepeatedField,
-                            TEST_ENTITY_PARAM_VALUE_CONVERTER,
-                            TEST_ENTITY_CONVERTER)
+                            TEST_ENTITY_PARAM_VALUE_CONVERTER)
                     .build();
 
     @Test
     public void getAppAction_genericParameters() {
-        Map<String, Property<?>> property = new HashMap<>();
-        property.put(
-                "requiredEntity",
-                new Property.Builder<TestEntity>()
-                        .setRequired(true)
-                        .setPossibleValues(
-                                new TestEntity.Builder().setId("one").setName("one").build())
-                        .build());
-        property.put(
-                "optionalEntity",
-                new Property.Builder<TestEntity>()
-                        .setRequired(true)
-                        .setPossibleValues(
-                                new TestEntity.Builder().setId("two").setName("two").build())
-                        .build());
-        property.put(
-                "repeatedEntities",
-                new Property.Builder<TestEntity>()
-                        .setRequired(true)
-                        .setPossibleValues(
-                                new TestEntity.Builder().setId("three").setName("three").build())
-                        .build());
+        List<BoundProperty<?>> boundProperties = new ArrayList<>();
+        boundProperties.add(
+                new BoundProperty<>(
+                        "requiredEntity",
+                        new Property.Builder<TestEntity>()
+                                .setRequired(true)
+                                .setPossibleValues(
+                                        new TestEntity.Builder()
+                                                .setId("one")
+                                                .setName("one")
+                                                .build())
+                                .build(),
+                        TEST_ENTITY_CONVERTER));
+        boundProperties.add(
+                new BoundProperty<>(
+                        "optionalEntity",
+                        new Property.Builder<TestEntity>()
+                                .setRequired(true)
+                                .setPossibleValues(
+                                        new TestEntity.Builder()
+                                                .setId("two")
+                                                .setName("two")
+                                                .build())
+                                .build(),
+                        TEST_ENTITY_CONVERTER));
+        boundProperties.add(
+                new BoundProperty(
+                        "repeatedEntities",
+                        new Property.Builder<TestEntity>()
+                                .setRequired(true)
+                                .setPossibleValues(
+                                        new TestEntity.Builder()
+                                                .setId("three")
+                                                .setName("three")
+                                                .build())
+                                .build(),
+                        TEST_ENTITY_CONVERTER));
 
-        assertThat(GENERIC_TYPES_ACTION_SPEC.convertPropertyToProto(property))
+        assertThat(
+                        GENERIC_TYPES_ACTION_SPEC.createAppAction(
+                                "testIdentifier", boundProperties, false))
                 .isEqualTo(
                         AppAction.newBuilder()
                                 .setName("actions.intent.TEST")
+                                .setIdentifier("testIdentifier")
                                 .addParams(
                                         IntentParameter.newBuilder()
                                                 .setName("requiredEntity")
@@ -211,22 +195,28 @@
                                                                 .newBuilder()
                                                                 .setIdentifier("three")
                                                                 .setName("three")))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(false))
                                 .build());
     }
 
     @Test
     public void getAppAction_onlyRequiredProperty() {
-        Map<String, Property<?>> property = new HashMap<>();
-        property.put("requiredString",
-                new Property.Builder<StringValue>()
-                        .setPossibleValues(StringValue.of("Donald"))
-                        .setValueMatchRequired(true)
-                        .build());
+        List<BoundProperty<?>> boundProperties = new ArrayList<>();
+        boundProperties.add(
+                new BoundProperty<>(
+                        "requiredString",
+                        new Property.Builder<StringValue>()
+                                .setPossibleValues(StringValue.of("Donald"))
+                                .setValueMatchRequired(true)
+                                .build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER));
 
-        assertThat(ACTION_SPEC.convertPropertyToProto(property))
+        assertThat(ACTION_SPEC.createAppAction("testIdentifier", boundProperties, true))
                 .isEqualTo(
                         AppAction.newBuilder()
                                 .setName("actions.intent.TEST")
+                                .setIdentifier("testIdentifier")
                                 .addParams(
                                         IntentParameter.newBuilder()
                                                 .setName("requiredString")
@@ -236,26 +226,39 @@
                                                                 .newBuilder()
                                                                 .setIdentifier("Donald")
                                                                 .setName("Donald")))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
                                 .build());
     }
 
     @Test
     public void getAppAction_allProperties() {
-        Map<String, Property<?>> property = new HashMap<>();
-        property.put("requiredString",
-                new Property.Builder<StringValue>().build());
-        property.put("optionalString",
-                new Property.Builder<StringValue>()
-                        .setPossibleValues(StringValue.of("value1"))
-                        .setValueMatchRequired(true)
-                        .setRequired(true)
-                        .build());
-        property.put("repeatedString", Property.prohibited());
+        List<BoundProperty<?>> boundProperties = new ArrayList<>();
+        boundProperties.add(
+                new BoundProperty<>(
+                        "requiredString",
+                        new Property.Builder<StringValue>().build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER));
+        boundProperties.add(
+                new BoundProperty<>(
+                        "optionalString",
+                        new Property.Builder<StringValue>()
+                                .setPossibleValues(StringValue.of("value1"))
+                                .setValueMatchRequired(true)
+                                .setRequired(true)
+                                .build(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER));
+        boundProperties.add(
+                new BoundProperty<>(
+                        "repeatedString",
+                        Property.prohibited(),
+                        TypeConverters.STRING_VALUE_ENTITY_CONVERTER));
 
-        assertThat(ACTION_SPEC.convertPropertyToProto(property))
+        assertThat(ACTION_SPEC.createAppAction("testIdentifier", boundProperties, false))
                 .isEqualTo(
                         AppAction.newBuilder()
                                 .setName("actions.intent.TEST")
+                                .setIdentifier("testIdentifier")
                                 .addParams(IntentParameter.newBuilder().setName("requiredString"))
                                 .addParams(
                                         IntentParameter.newBuilder()
@@ -272,6 +275,8 @@
                                         IntentParameter.newBuilder()
                                                 .setName("repeatedString")
                                                 .setIsProhibited(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(false))
                                 .build());
     }
 
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
index 744863d..13c2c0a 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
@@ -38,6 +38,7 @@
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeSpec
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.impl.spec.BoundProperty
 import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import androidx.appactions.interaction.capabilities.core.testing.spec.Arguments
@@ -80,11 +81,10 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@Suppress("UNCHECKED_CAST")
 class TaskCapabilityImplTest {
     private val capability: Capability =
         createCapability<EmptyTaskUpdater>(
-            SINGLE_REQUIRED_FIELD_PROPERTY,
+            SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
             sessionFactory =
             { _ ->
                 object : ExecutionSession {
@@ -124,12 +124,15 @@
     fun appAction_computedProperty() {
         val mutableEntityList = mutableListOf<StringValue>()
         val capability = createCapability<EmptyTaskUpdater>(
-            mutableMapOf(
-                "required" to Property
-                    .Builder<StringValue>()
-                    .setPossibleValueSupplier(
-                        mutableEntityList::toList
-                    ).build()
+            listOf(
+                BoundProperty(
+                    "required",
+                    Property.Builder<StringValue>()
+                        .setPossibleValueSupplier(
+                            mutableEntityList::toList
+                        ).build(),
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
             ),
             sessionFactory =
             {
@@ -183,7 +186,7 @@
         val externalSession = object : ExecutionSession {}
         val capability =
             createCapability(
-                SINGLE_REQUIRED_FIELD_PROPERTY,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 { externalSession },
                 { TaskHandler.Builder<Arguments, Confirmation>().build() },
                 ::EmptyTaskUpdater
@@ -198,7 +201,7 @@
         val >
         val capability: Capability =
             createCapability(
-                SINGLE_REQUIRED_FIELD_PROPERTY,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory =
                 { _ ->
                     object : ExecutionSession {
@@ -289,7 +292,7 @@
             }
         }
         val capability: Capability = createCapability(
-            SINGLE_REQUIRED_FIELD_PROPERTY,
+            SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
             sessionFactory = { _ -> externalSession },
             sessionBridge = SessionBridge {
                 TaskHandler.Builder<Arguments, Confirmation>().build()
@@ -321,7 +324,7 @@
     fun fulfillmentType_unknown_errorReported() {
         val capability: Capability =
             createCapability(
-                SINGLE_REQUIRED_FIELD_PROPERTY,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory =
                 { _ ->
                     object : ExecutionSession {
@@ -361,13 +364,21 @@
 
     @Test
     fun slotFilling_isActive_smokeTest() {
-        val property = mapOf(
-            "stringSlotA" to Property.Builder<StringValue>()
-                .setRequired(true)
-                .build(),
-            "stringSlotB" to Property.Builder<StringValue>()
-                .setRequired(true)
-                .build()
+        val boundProperties = listOf(
+            BoundProperty(
+                "stringSlotA",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            ),
+            BoundProperty(
+                "stringSlotB",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            )
         )
         val sessionFactory:
             (hostProperties: HostProperties?) -> CapabilityTwoStrings.ExecutionSession =
@@ -404,7 +415,7 @@
             TaskCapabilityImpl(
                 "fakeId",
                 CapabilityTwoStrings.ACTION_SPEC,
-                property,
+                boundProperties,
                 sessionFactory,
                 sessionBridge,
                 ::EmptyTaskUpdater
@@ -454,13 +465,21 @@
     @kotlin.Throws(Exception::class)
     fun slotFilling_optionalButRejectedParam_onFinishNotInvoked() {
         val >
-        val property = mapOf(
-            "stringSlotA" to Property.Builder<StringValue>()
-                .setRequired(true)
-                .build(),
-            "stringSlotB" to Property.Builder<StringValue>()
-                .setRequired(false)
-                .build()
+        val boundProperties = listOf(
+            BoundProperty(
+                "stringSlotA",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            ),
+            BoundProperty(
+                "stringSlotB",
+                Property.Builder<StringValue>()
+                    .setRequired(false)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            )
         )
         val sessionFactory:
             (hostProperties: HostProperties?) -> CapabilityTwoStrings.ExecutionSession =
@@ -499,7 +518,7 @@
             TaskCapabilityImpl(
                 "fakeId",
                 CapabilityTwoStrings.ACTION_SPEC,
-                property,
+                boundProperties,
                 sessionFactory,
                 sessionBridge,
                 ::EmptyTaskUpdater
@@ -543,19 +562,26 @@
     @Test
     @kotlin.Throws(Exception::class)
     fun slotFilling_assistantRemovedParam_clearInSdkState() {
-        val property = mapOf(
-            "required" to
+        val boundProperties = listOf(
+            BoundProperty(
+                "required",
                 Property.Builder<StringValue>()
                     .setRequired(true)
                     .build(),
-            "optionalEnum" to Property.Builder<TestEnum>()
-                .setPossibleValues(TestEnum.VALUE_1, TestEnum.VALUE_2)
-                .setRequired(true)
-                .build()
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            ),
+            BoundProperty(
+                "optionalEnum",
+                Property.Builder<TestEnum>()
+                    .setPossibleValues(TestEnum.VALUE_1, TestEnum.VALUE_2)
+                    .setRequired(true)
+                    .build(),
+                { Entity.newBuilder().setIdentifier(it.toString()).build() }
+            )
         )
         val capability: Capability =
             createCapability(
-                property,
+                boundProperties,
                 sessionFactory = { _ -> ExecutionSession.DEFAULT },
                 sessionBridge = SessionBridge {
                     TaskHandler.Builder<Arguments, Confirmation>().build()
@@ -609,7 +635,7 @@
     fun disambig_singleParam_disambigEntitiesInContext() {
         val capability: Capability =
             createCapability(
-                SINGLE_REQUIRED_FIELD_PROPERTY,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = {
                     object : ExecutionSession {
                         override suspend fun onExecute(arguments: Arguments) =
@@ -740,15 +766,21 @@
     @kotlin.Throws(Exception::class)
     @Suppress("DEPRECATION")
     fun identifierOnly_refillsStruct() = runBlocking<Unit> {
-        val property = mapOf(
-            "listItem" to Property.Builder<
-                ListItem
-                >()
-                .setRequired(true)
-                .build(),
-            "anyString" to Property.Builder<StringValue>()
-                .setRequired(true)
-                .build()
+        val boundProperties = listOf(
+            BoundProperty(
+                "listItem",
+                Property.Builder<ListItem>()
+                    .setRequired(true)
+                    .build(),
+                EntityConverter.of(TypeConverters.LIST_ITEM_TYPE_SPEC)
+            ),
+            BoundProperty(
+                "string",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            )
         )
         val item1: ListItem = ListItem.Builder().setName("red apple").setIdentifier("item1").build()
         val item2: ListItem =
@@ -765,7 +797,7 @@
                         arguments: CapabilityStructFill.Arguments
                     ): ExecutionResult<CapabilityStructFill.Output> {
                         arguments.listItem?.let { onExecuteListItemDeferred.complete(it) }
-                        arguments.anyString?.let { onExecuteStringDeferred.complete(it) }
+                        arguments.string?.let { onExecuteStringDeferred.complete(it) }
                         return ExecutionResult.Builder<CapabilityStructFill.Output>().build()
                     }
 
@@ -813,7 +845,7 @@
             TaskCapabilityImpl(
                 "selectListItem",
                 CapabilityStructFill.ACTION_SPEC,
-                property,
+                boundProperties,
                 sessionFactory,
                 sessionBridge,
                 ::EmptyTaskUpdater
@@ -996,12 +1028,9 @@
                         ExecutionResult.Builder<Output>().build()
                 }
             }
-        val property = mapOf(
-            "required" to Property.Builder<StringValue>().setRequired(true).build()
-        )
         val capability: Capability =
             createCapability(
-                property,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = sessionFactory,
                 sessionBridge = SessionBridge {
                     TaskHandler.Builder<Arguments, Confirmation>().build()
@@ -1056,12 +1085,9 @@
                         .build()
                 }
             }
-        val property = mapOf(
-            "required" to Property.Builder<StringValue>().setRequired(true).build()
-        )
         val capability: Capability =
             createCapability(
-                property,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = sessionFactory,
                 sessionBridge = SessionBridge {
                     TaskHandler.Builder<Arguments, Confirmation>()
@@ -1106,12 +1132,9 @@
                         ExecutionResult.Builder<Output>().build()
                 }
             }
-        val property = mapOf(
-            "required" to Property.Builder<StringValue>().setRequired(true).build()
-        )
         val capability: Capability =
             createCapability(
-                property,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = sessionFactory,
                 sessionBridge = SessionBridge {
                     TaskHandler.Builder<Arguments, Confirmation>().build()
@@ -1152,7 +1175,7 @@
     fun syncStatus_unknown_errorReported() {
         val capability: Capability =
             createCapability(
-                SINGLE_REQUIRED_FIELD_PROPERTY,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory =
                     { _ ->
                         object : ExecutionSession {
@@ -1198,9 +1221,6 @@
     @Test
     @kotlin.Throws(Exception::class)
     fun syncStatus_slotsIncomplete_taskNotExecuted() {
-        val property = mapOf(
-            "required" to Property.Builder<StringValue>().setRequired(true).build()
-        )
         val >
         val sessionFactory: (hostProperties: HostProperties?) -> ExecutionSession =
             { _ ->
@@ -1213,7 +1233,7 @@
             }
         val capability: Capability =
             createCapability(
-                property,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = sessionFactory,
                 sessionBridge = SessionBridge { TaskHandler.Builder<Arguments, Confirmation>()
                     .build() },
@@ -1288,9 +1308,6 @@
                         .build()
                 }
             }
-        val property = mapOf(
-            "required" to Property.Builder<StringValue>().setRequired(true).build()
-        )
         val >
         val sessionFactory: (hostProperties: HostProperties?) -> ExecutionSession =
             { _ ->
@@ -1309,7 +1326,7 @@
             }
         val capability: Capability =
             createCapability(
-                property,
+                SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES,
                 sessionFactory = sessionFactory,
                 sessionBridge = {
                                     TaskHandler.Builder<Arguments, Confirmation>()
@@ -1508,7 +1525,13 @@
             >(ACTION_SPEC) {
 
         init {
-            setProperty(SINGLE_REQUIRED_FIELD_PROPERTY)
+            setProperty(
+                "required",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+            )
         }
 
         override val sessionBridge: SessionBridge<
@@ -1594,39 +1617,23 @@
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "required",
-                    { properties ->
-                        properties["required"] as Property<StringValue>
-                    },
                     Arguments.Builder::setRequiredStringField,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "optional",
-                    { properties ->
-                        properties["optional"] as? Property<StringValue>
-                    },
                     Arguments.Builder::setOptionalStringField,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "optionalEnum",
-                    { properties ->
-                        properties["optionalEnum"] as? Property<TestEnum>
-                    },
                     Arguments.Builder::setEnumField,
-                    ENUM_CONVERTER,
-                    { Entity.newBuilder().setIdentifier(it.toString()).build() }
+                    ENUM_CONVERTER
                 )
                 .bindRepeatedParameter(
                     "repeated",
-                    { properties ->
-                        properties["repeated"] as? Property<StringValue>
-                    },
                     Arguments.Builder::setRepeatedStringField,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "optionalStringOutput",
@@ -1640,10 +1647,13 @@
                 )
                 .build()
 
-        private val SINGLE_REQUIRED_FIELD_PROPERTY = mapOf(
-            "required" to Property.Builder<StringValue>()
-                .setRequired(true)
-                .build()
+        private val SINGLE_REQUIRED_FIELD_BOUND_PROPERTIES = listOf(
+            BoundProperty(
+                "required",
+                Property.Builder<StringValue>()
+                    .setRequired(true)
+                    .build(),
+                TypeConverters.STRING_VALUE_ENTITY_CONVERTER)
         )
 
         private fun getCurrentValues(
@@ -1664,7 +1674,7 @@
          * etc., defined under ../../testing/spec
          */
         private fun <SessionUpdaterT : AbstractTaskUpdater> createCapability(
-            property: Map<String, Property<*>>,
+            boundProperties: List<BoundProperty<*>>,
             sessionFactory: (hostProperties: HostProperties?) -> ExecutionSession,
             sessionBridge: SessionBridge<ExecutionSession, Arguments, Confirmation>,
             sessionUpdaterSupplier: Supplier<SessionUpdaterT>
@@ -1678,7 +1688,7 @@
             return TaskCapabilityImpl(
                 "id",
                 ACTION_SPEC,
-                property,
+                boundProperties,
                 sessionFactory,
                 sessionBridge,
                 sessionUpdaterSupplier
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
index 40d0f99..878980f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
@@ -20,12 +20,9 @@
 import androidx.appactions.interaction.capabilities.core.AppEntityListener
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
-import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Property
-import androidx.appactions.interaction.capabilities.core.properties.StringValue
 
 private const val CAPABILITY_NAME = "actions.intent.TEST"
 
@@ -34,11 +31,11 @@
 
     class Arguments internal constructor(
         val listItem: ListItem?,
-        val anyString: String?
+        val string: String?
     ) {
         override fun toString(): String {
             return "Arguments(listItem=$listItem, " +
-                "anyString=$anyString)"
+                "string=$string)"
         }
 
         override fun equals(other: Any?): Boolean {
@@ -48,27 +45,27 @@
             other as Arguments
 
             if (listItem != other.listItem) return false
-            if (anyString != other.anyString) return false
+            if (string != other.string) return false
             return true
         }
 
         override fun hashCode(): Int {
             var result = listItem.hashCode()
-            result += 31 * anyString.hashCode()
+            result += 31 * string.hashCode()
             return result
         }
 
         class Builder : BuilderOf<Arguments> {
             private var listItem: ListItem? = null
-            private var anyString: String? = null
+            private var string: String? = null
 
             fun setListItem(listItem: ListItem): Builder =
                 apply { this.listItem = listItem }
 
             fun setAnyString(stringSlotB: String): Builder =
-                apply { this.anyString = stringSlotB }
+                apply { this.string = stringSlotB }
 
-            override fun build(): Arguments = Arguments(listItem, anyString)
+            override fun build(): Arguments = Arguments(listItem, string)
         }
     }
 
@@ -81,27 +78,18 @@
     }
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
             .setArguments(Arguments::class.java, Arguments::Builder)
             .setOutput(Output::class.java)
             .bindParameter(
                 "listItem",
-                { properties ->
-                    properties["listItem"] as? Property<ListItem>
-                },
                 Arguments.Builder::setListItem,
-                ParamValueConverter.of(TypeConverters.LIST_ITEM_TYPE_SPEC),
-                EntityConverter.of(TypeConverters.LIST_ITEM_TYPE_SPEC)::convert
+                ParamValueConverter.of(TypeConverters.LIST_ITEM_TYPE_SPEC)
             )
             .bindParameter(
                 "string",
-                { properties ->
-                    properties["anyString"] as? Property<StringValue>
-                },
                 Arguments.Builder::setAnyString,
-                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER
             )
             .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
index 612d3ca..3b1766f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
@@ -21,8 +21,6 @@
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Property
-import androidx.appactions.interaction.capabilities.core.properties.StringValue
 
 private const val CAPABILITY_NAME = "actions.intent.TEST"
 
@@ -76,27 +74,18 @@
     interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
             .setArguments(Arguments::class.java, Arguments::Builder)
             .setOutput(Output::class.java)
             .bindParameter(
                 "stringSlotA",
-                { properties ->
-                    properties["stringSlotA"] as? Property<StringValue>
-                },
                 Arguments.Builder::setStringSlotA,
-                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER
             )
             .bindParameter(
                 "stringSlotB",
-                { properties ->
-                    properties["stringSlotB"] as? Property<StringValue>
-                },
                 Arguments.Builder::setStringSlotB,
-                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER
             )
             .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
index 4fd5dc7..17da4f7 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
@@ -39,18 +39,17 @@
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
+        fun setStartTimeProperty(startTime: Property<LocalTime>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.START_TIME.key,
+            startTime,
+            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+        )
 
-        fun setStartTime(startTime: Property<LocalTime>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.START_TIME.key] = startTime }
-
-        fun setEndTime(endTime: Property<LocalTime>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.END_TIME.key] = endTime }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setEndTimeProperty(endTime: Property<LocalTime>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.END_TIME.key,
+            endTime,
+            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -101,7 +100,6 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
@@ -111,21 +109,13 @@
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "exerciseObservation.startTime",
-                    { properties ->
-                        properties[PropertyMapStrings.START_TIME.key] as? Property<LocalTime>
-                    },
                     Arguments.Builder::setStartTime,
-                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "exerciseObservation.endTime",
-                    { properties ->
-                        properties[PropertyMapStrings.END_TIME.key] as? Property<LocalTime>
-                    },
                     Arguments.Builder::setEndTime,
-                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
index 7d843ac..053d7551 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
@@ -44,18 +44,17 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
+        fun setStartTimeProperty(startTime: Property<LocalTime>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.START_TIME.key,
+            startTime,
+            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+        )
 
-        fun setStartTime(startTime: Property<LocalTime>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.START_TIME.key] = startTime }
-
-        fun setEndTime(endTime: Property<LocalTime>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.END_TIME.key] = endTime }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setEndTimeProperty(endTime: Property<LocalTime>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.END_TIME.key,
+            endTime,
+            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -106,7 +105,6 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
@@ -116,21 +114,13 @@
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "healthObservation.startTime",
-                    { properties ->
-                        properties[PropertyMapStrings.START_TIME.key] as? Property<LocalTime>
-                    },
                     Arguments.Builder::setStartTime,
-                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "healthObservation.endTime",
-                    { properties ->
-                        properties[PropertyMapStrings.END_TIME.key] as? Property<LocalTime>
-                    },
                     Arguments.Builder::setEndTime,
-                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
index 8a94643d..d645532 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
@@ -42,14 +42,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-        fun setName(name: Property<StringValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.NAME.key] = name }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.NAME.key,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -92,19 +89,14 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "exercise.name",
-                    { properties ->
-                        properties[PropertyMapStrings.NAME.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setName,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
index 963f7ad..aae48fb 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
@@ -42,14 +42,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-        fun setName(name: Property<StringValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.NAME.key] = name }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.NAME.key,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -92,19 +89,14 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "exercise.name",
-                    { properties ->
-                        properties[PropertyMapStrings.NAME.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setName,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
index 3de87ec..cb4e825 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
@@ -44,18 +44,17 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.NAME.key,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
 
-        fun setName(name: Property<StringValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.NAME.key] = name }
-
-        fun setDuration(duration: Property<Duration>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.DURATION.key] = duration }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setDurationProperty(duration: Property<Duration>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.DURATION.key,
+            duration,
+            TypeConverters.DURATION_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -106,28 +105,19 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "exercise.duration",
-                    { properties ->
-                        properties[PropertyMapStrings.DURATION.key] as? Property<Duration>
-                    },
                     Arguments.Builder::setDuration,
-                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-                    TypeConverters.DURATION_ENTITY_CONVERTER
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "exercise.name",
-                    { properties ->
-                        properties[PropertyMapStrings.NAME.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setName,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
index e257fac..cd125c2 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
@@ -42,15 +42,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setName(name: Property<StringValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.NAME.key] = name }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.NAME.key,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -93,19 +89,14 @@
 
     companion object {
         // TODO(b/273602015): Update to use Name property from builtintype library.
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "exercise.name",
-                    { properties ->
-                        properties[PropertyMapStrings.NAME.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setName,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
index e2f8e01..e65e144 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
@@ -18,10 +18,12 @@
 
 import androidx.appactions.builtintypes.experimental.types.GenericErrorStatus
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
+import androidx.appactions.builtintypes.experimental.types.Timer
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
 import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -35,7 +37,7 @@
 @CapabilityFactory(name = CAPABILITY_NAME)
 class PauseTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
-        TIMER_LIST("timer.timerList")
+        TIMER("timer")
     }
 
     class CapabilityBuilder :
@@ -46,15 +48,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setTimerListProperty(timerList: Property<TimerValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.TIMER_LIST.key] = timerList }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setTimerProperty(timer: Property<Timer>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.TIMER.key,
+            timer,
+            EntityConverter.of(TypeConverters.TIMER_TYPE_SPEC)
+        )
     }
 
     class Arguments
@@ -156,19 +154,14 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     "timer",
-                    { properties ->
-                        properties[PropertyMapStrings.TIMER_LIST.key] as? Property<TimerValue>
-                    },
                     Arguments.Builder::setTimerList,
-                    TimerValue.PARAM_VALUE_CONVERTER,
-                    TimerValue.ENTITY_CONVERTER
+                    TimerValue.PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "executionStatus",
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
index 5a3f115..cb718a1 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
@@ -18,10 +18,12 @@
 
 import androidx.appactions.builtintypes.experimental.types.GenericErrorStatus
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
+import androidx.appactions.builtintypes.experimental.types.Timer
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
 import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -35,7 +37,7 @@
 @CapabilityFactory(name = CAPABILITY_NAME)
 class ResetTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
-        TIMER_LIST("timer.timerList")
+        TIMER("timer")
     }
 
     class CapabilityBuilder :
@@ -46,15 +48,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setTimerListProperty(timerList: Property<TimerValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.TIMER_LIST.key] = timerList }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setTimerProperty(timer: Property<Timer>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.TIMER.key,
+            timer,
+            EntityConverter.of(TypeConverters.TIMER_TYPE_SPEC)
+        )
     }
 
     class Arguments internal constructor(val timerList: List<TimerValue>?) {
@@ -153,19 +151,14 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     "timer",
-                    { properties ->
-                        properties[PropertyMapStrings.TIMER_LIST.key] as? Property<TimerValue>
-                    },
                     Arguments.Builder::setTimerList,
-                    TimerValue.PARAM_VALUE_CONVERTER,
-                    TimerValue.ENTITY_CONVERTER
+                    TimerValue.PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "executionStatus",
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
index 505bdec..fac9ab4 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
@@ -18,10 +18,12 @@
 
 import androidx.appactions.builtintypes.experimental.types.GenericErrorStatus
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
+import androidx.appactions.builtintypes.experimental.types.Timer
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
 import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -35,7 +37,7 @@
 @CapabilityFactory(name = CAPABILITY_NAME)
 class ResumeTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
-        TIMER_LIST("timer.timerList")
+        TIMER("timer")
     }
 
     class CapabilityBuilder :
@@ -46,15 +48,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setTimerListProperty(timerList: Property<TimerValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.TIMER_LIST.key] = timerList }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setTimerProperty(timer: Property<Timer>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.TIMER.key,
+            timer,
+            EntityConverter.of(TypeConverters.TIMER_TYPE_SPEC)
+        )
     }
 
     class Arguments internal constructor(val timerList: List<TimerValue>?) {
@@ -153,19 +151,14 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     "timer",
-                    { properties ->
-                        properties[PropertyMapStrings.TIMER_LIST.key] as? Property<TimerValue>
-                    },
                     Arguments.Builder::setTimerList,
-                    TimerValue.PARAM_VALUE_CONVERTER,
-                    TimerValue.ENTITY_CONVERTER
+                    TimerValue.PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "executionStatus",
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
index 6d2bfa7..85ff7bc 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
@@ -41,7 +41,6 @@
 @CapabilityFactory(name = CAPABILITY_NAME)
 class StartTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
-        TIMER_LIST("timer.timerList"),
         IDENTIFIER("timer.identifier"),
         NAME("timer.name"),
         DURATION("timer.duration")
@@ -55,8 +54,6 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
         override val sessionBridge: SessionBridge<
             ExecutionSession,
             Arguments,
@@ -67,26 +64,25 @@
             sessionFactory: (hostProperties: HostProperties?) -> ExecutionSession
         ): CapabilityBuilder = super.setExecutionSessionFactory(sessionFactory)
 
-        fun setTimerListProperty(timerList: Property<TimerValue>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.TIMER_LIST.key] = timerList
-        }
+        fun setIdentifierProperty(
+            identifier: Property<StringValue>
+        ): CapabilityBuilder = setProperty(
+            PropertyMapStrings.IDENTIFIER.key,
+            identifier,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
 
-        fun setIdentifierProperty(identifier: Property<StringValue>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.IDENTIFIER.key] = identifier
-        }
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.NAME.key,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
 
-        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.NAME.key] = name
-        }
-
-        fun setDurationProperty(duration: Property<Duration>): CapabilityBuilder = apply {
-            properties[PropertyMapStrings.DURATION.key] = duration
-        }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setDurationProperty(duration: Property<Duration>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.DURATION.key,
+            duration,
+            TypeConverters.DURATION_ENTITY_CONVERTER
+        )
     }
 
     interface ExecutionSession : BaseExecutionSession<Arguments, Output> {
@@ -203,37 +199,24 @@
     class Confirmation internal constructor()
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "timer.identifier",
-                    { properties ->
-                        properties[PropertyMapStrings.IDENTIFIER.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setIdentifier,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "timer.name",
-                    { properties ->
-                        properties[PropertyMapStrings.NAME.key] as? Property<StringValue>
-                    },
                     Arguments.Builder::setName,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "timer.duration",
-                    { properties ->
-                        properties[PropertyMapStrings.DURATION.key] as? Property<Duration>
-                    },
                     Arguments.Builder::setDuration,
-                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-                    TypeConverters.DURATION_ENTITY_CONVERTER
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "executionStatus",
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
index 5897c720..f75a521 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
@@ -18,10 +18,12 @@
 
 import androidx.appactions.builtintypes.experimental.types.GenericErrorStatus
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
+import androidx.appactions.builtintypes.experimental.types.Timer
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
 import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -35,7 +37,7 @@
 @CapabilityFactory(name = CAPABILITY_NAME)
 class StopTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
-        TIMER_LIST("timer.timerList")
+        TIMER("timer")
     }
 
     class CapabilityBuilder :
@@ -46,15 +48,11 @@
             Confirmation,
             ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        fun setTimerListProperty(timerList: Property<TimerValue>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.TIMER_LIST.key] = timerList }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setTimerProperty(timer: Property<Timer>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.TIMER.key,
+            timer,
+            EntityConverter.of(TypeConverters.TIMER_TYPE_SPEC)
+        )
     }
 
     class Arguments internal constructor(val timerList: List<TimerValue>?) {
@@ -153,19 +151,14 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     "timer",
-                    { properties ->
-                        properties[PropertyMapStrings.TIMER_LIST.key] as? Property<TimerValue>
-                    },
                     Arguments.Builder::setTimerList,
-                    TimerValue.PARAM_VALUE_CONVERTER,
-                    TimerValue.ENTITY_CONVERTER
+                    TimerValue.PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "executionStatus",
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
index b16d175..2132685 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
@@ -18,7 +18,6 @@
 
 import androidx.appactions.builtintypes.experimental.types.Timer
 import androidx.appactions.interaction.capabilities.core.SearchAction
-import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.converters.UnionTypeSpec
@@ -66,7 +65,5 @@
                 .build()
 
         internal val PARAM_VALUE_CONVERTER = ParamValueConverter.of(TYPE_SPEC)
-
-        internal val ENTITY_CONVERTER = EntityConverter.of(TYPE_SPEC)
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/PauseTimerTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/PauseTimerTest.kt
index 74a8f0e..2abbc5c 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/PauseTimerTest.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/PauseTimerTest.kt
@@ -49,7 +49,7 @@
         val argsDeferred = CompletableDeferred<Arguments>()
         val capability = PauseTimer.CapabilityBuilder()
             .setId("pause timer")
-            .setTimerListProperty(Property.Builder<TimerValue>().setRequired(true).build())
+            .setTimerProperty(Property.Builder<Timer>().setRequired(true).build())
             .setExecutionCallback(
                 ExecutionCallback {
                     argsDeferred.complete(it)
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResetTimerTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResetTimerTest.kt
index 163f560..6300a51 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResetTimerTest.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResetTimerTest.kt
@@ -49,7 +49,7 @@
         val argsDeferred = CompletableDeferred<Arguments>()
         val capability = ResetTimer.CapabilityBuilder()
             .setId("reset timer")
-            .setTimerListProperty(Property.Builder<TimerValue>().setRequired(true).build())
+            .setTimerProperty(Property.Builder<Timer>().setRequired(true).build())
             .setExecutionCallback(
                 ExecutionCallback {
                     argsDeferred.complete(it)
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimerTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimerTest.kt
index 8e82cdf..678b27b 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimerTest.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimerTest.kt
@@ -49,7 +49,7 @@
         val argsDeferred = CompletableDeferred<Arguments>()
         val capability = ResumeTimer.CapabilityBuilder()
             .setId("resume timer")
-            .setTimerListProperty(Property.Builder<TimerValue>().setRequired(true).build())
+            .setTimerProperty(Property.Builder<Timer>().setRequired(true).build())
             .setExecutionCallback(
                 ExecutionCallback {
                     argsDeferred.complete(it)
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/StopTimerTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/StopTimerTest.kt
index 343ee54f..0699e70 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/StopTimerTest.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/StopTimerTest.kt
@@ -49,7 +49,7 @@
         val argsDeferred = CompletableDeferred<Arguments>()
         val capability = StopTimer.CapabilityBuilder()
             .setId("stop timer")
-            .setTimerListProperty(Property.Builder<TimerValue>().setRequired(true).build())
+            .setTimerProperty(Property.Builder<Timer>().setRequired(true).build())
             .setExecutionCallback(
                 ExecutionCallback {
                     argsDeferred.complete(it)
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
index 704cbf1..423715b 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
@@ -25,7 +25,6 @@
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.safety.executionstatus.EmergencySharingInProgress
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyAccountNotLoggedIn
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyFeatureNotOnboarded
@@ -41,14 +40,7 @@
     class CapabilityBuilder :
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession,
-            >(ACTION_SPEC) {
-
-        private var properties = mutableMapOf<String, Property<*>>()
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
-    }
+            >(ACTION_SPEC)
 
     class Arguments internal constructor() {
         class Builder : BuilderOf<Arguments> {
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
index 63737d9..faa7eaf 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
@@ -53,18 +53,19 @@
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession
             >(ACTION_SPEC) {
-        private var properties = mutableMapOf<String, Property<*>>()
+        fun setDurationProperty(duration: Property<Duration>): CapabilityBuilder = setProperty(
+            PropertyMapStrings.DURATION.key,
+            duration,
+            TypeConverters.DURATION_ENTITY_CONVERTER
+        )
 
-        fun setDuration(duration: Property<Duration>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.DURATION.key] = duration }
-
-        fun setCheckInTime(checkInTime: Property<ZonedDateTime>): CapabilityBuilder =
-            apply { properties[PropertyMapStrings.CHECK_IN_TIME.key] = checkInTime }
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
+        fun setCheckInTimeProperty(
+            checkInTime: Property<ZonedDateTime>
+        ): CapabilityBuilder = setProperty(
+            PropertyMapStrings.CHECK_IN_TIME.key,
+            checkInTime,
+            TypeConverters.ZONED_DATETIME_ENTITY_CONVERTER
+        )
     }
 
     class Arguments internal constructor(
@@ -225,28 +226,19 @@
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(Arguments::class.java, Arguments::Builder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "safetyCheck.duration",
-                    { properties ->
-                        properties[PropertyMapStrings.DURATION.key] as? Property<Duration>
-                    },
                     Arguments.Builder::setDuration,
-                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-                    TypeConverters.DURATION_ENTITY_CONVERTER
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER
                 )
                 .bindParameter(
                     "safetyCheck.checkInTime",
-                    { properties ->
-                        properties[PropertyMapStrings.CHECK_IN_TIME.key] as? Property<ZonedDateTime>
-                    },
                     Arguments.Builder::setCheckInTime,
-                    TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER,
-                    TypeConverters.ZONED_DATETIME_ENTITY_CONVERTER
+                    TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER
                 )
                 .bindOutput(
                     "safetyCheck",
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
index e35c3b5..90b7827 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
@@ -26,7 +26,6 @@
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyAccountNotLoggedIn
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyFeatureNotOnboarded
 import androidx.appactions.interaction.proto.ParamValue
@@ -41,15 +40,7 @@
     class CapabilityBuilder :
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession,
-            >(ACTION_SPEC) {
-
-        private var properties = mutableMapOf<String, Property<*>>()
-
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
-    }
+            >(ACTION_SPEC)
 
     class Arguments internal constructor() {
         class Builder : BuilderOf<Arguments> {
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
index 7734f23..e8bb0a9 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
@@ -26,7 +26,6 @@
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyAccountNotLoggedIn
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyFeatureNotOnboarded
 import androidx.appactions.interaction.proto.ParamValue
@@ -41,14 +40,7 @@
     class CapabilityBuilder :
         Capability.Builder<
             CapabilityBuilder, Arguments, Output, Confirmation, ExecutionSession
-            >(ACTION_SPEC) {
-
-        private var properties = mutableMapOf<String, Property<*>>()
-        override fun build(): Capability {
-            super.setProperty(properties)
-            return super.build()
-        }
-    }
+            >(ACTION_SPEC)
 
     class Arguments internal constructor() {
         class Builder : BuilderOf<Arguments> {
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
index 1105d83..47edace 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
@@ -30,10 +30,6 @@
 private const val CAPABILITY_NAME = "actions.intent.FAKE_CAPABILITY"
 
 class FakeCapability private constructor() {
-    class Properties(
-        val fieldOne: Property<StringValue>? = null,
-    )
-
     class Arguments internal constructor(
         val fieldOne: String?,
     ) {
@@ -75,35 +71,21 @@
             builder.build()
         }
 
-        private var fieldOne: Property<StringValue>? = null
-
-        fun setFieldOne(fieldOne: Property<StringValue>) = apply {
-            this.fieldOne = fieldOne
-        }
-
-        override fun build(): Capability {
-            super.setProperty(
-                mutableMapOf(
-                    "fieldOne" to (
-                        fieldOne ?: Property.Builder<StringValue>().build()
-                        )
-                )
-            )
-            return super.build()
-        }
+        fun setFieldOne(fieldOne: Property<StringValue>) = setProperty(
+            "fieldOne",
+            fieldOne,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
     }
 
     companion object {
-        @Suppress("UNCHECKED_CAST")
         private val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
             .setArguments(Arguments::class.java, Arguments::Builder)
             .setOutput(Output::class.java)
             .bindParameter(
                 "fieldOne",
-                { properties -> properties["fieldOne"] as? Property<StringValue> },
                 Arguments.Builder::setFieldOne,
-                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER
             )
             .build()
     }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
index 00934a5..858ae55 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
@@ -141,7 +141,7 @@
      * }
      * ```
      */
-    override fun from(project: Project, variantName: String?) = main.from(project, variantName)
+    override fun from(project: Project) = main.from(project)
 
     fun variants(
         action: Action<NamedDomainObjectContainer<BaselineProfileVariantConfigurationImpl>>
@@ -160,7 +160,7 @@
     BaselineProfileVariantConfiguration {
 
     internal val filters = FilterRules()
-    internal val dependencies = mutableListOf<Pair<Project, String?>>()
+    internal val dependencies = mutableListOf<Project>()
 
     /**
      * @inheritDoc
@@ -175,8 +175,8 @@
     /**
      * @inheritDoc
      */
-    override fun from(project: Project, variantName: String?) {
-        dependencies.add(Pair(project, variantName))
+    override fun from(project: Project) {
+        dependencies.add(project)
     }
 }
 
@@ -321,24 +321,7 @@
      * }
      * ```
      */
-    fun from(project: Project) = from(project, null)
-
-    /**
-     * Allows to specify a target `com.android.test` module that has the `androidx.baselineprofile`
-     * plugin, and that can provide a baseline profile for this module. The [variantName] can
-     * directly map to a test variant, to fetch a baseline profile for a different variant.
-     * For example it's possible to use a `paidRelease` baseline profile for `freeRelease` variant.
-     * ```
-     * baselineProfile {
-     *     variants {
-     *         freeRelease {
-     *             from(project(":baseline-profile"), "paidRelease")
-     *         }
-     *     }
-     * }
-     * ```
-     */
-    fun from(project: Project, variantName: String?)
+    fun from(project: Project)
 }
 
 class FilterRules {
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
index a57d752..9ba3807 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
@@ -191,29 +191,13 @@
         // Creates the configuration to carry the specific variant artifact
         val baselineProfileConfiguration = createConfigurationForVariant(
             variant = variant,
-            mainConfiguration = mainBaselineProfileConfiguration,
-            hasDirectConfiguration = variantDependencies.any { it.second != null }
+            mainConfiguration = mainBaselineProfileConfiguration
         )
 
         // Adds the custom dependencies for baseline profiles. Note that dependencies
         // for global, build type, flavor and variant specific are all merged.
         variantDependencies.forEach {
-            val targetProject = it.first
-            val variantName = it.second
-            val targetProjectDependency = if (variantName != null) {
-                val configurationName = camelCase(
-                    variantName,
-                    CONFIGURATION_NAME_BASELINE_PROFILES
-                )
-                project.dependencies.project(
-                    mutableMapOf(
-                        "path" to targetProject.path,
-                        "configuration" to configurationName
-                    )
-                )
-            } else {
-                project.dependencyFactory.create(targetProject)
-            }
+            val targetProjectDependency = project.dependencyFactory.create(it)
             baselineProfileConfiguration.dependencies.add(targetProjectDependency)
         }
 
@@ -436,16 +420,26 @@
 
         if (supportsFeature(AgpFeature.TEST_MODULE_SUPPORTS_MULTIPLE_BUILD_TYPES)) {
 
-            // Generate a flavor task, such as `generateFreeBaselineProfile`
-            if (!mergeIntoMain &&
-                !variant.flavorName.isNullOrBlank() &&
-                variant.flavorName != variant.name
-            ) {
-                maybeCreateGenerateTask<Task>(
-                    project = project,
-                    variantName = variant.flavorName!!,
-                    lastTaskProvider = lastTaskProvider
+            // Generate a flavor task for the assembled flavor name and each flavor dimension.
+            // For example for variant `freeRelease` (build type `release`, flavor `free`):
+            // `generateFreeBaselineProfile`.
+            // For example for variant `freeRedRelease` (build type `release`, flavor dimensions
+            // `free` and `red`): `generateFreeBaselineProfile`, `generateRedBaselineProfile` and
+            // `generateFreeRedBaselineProfile`.
+            if (!mergeIntoMain) {
+                listOfNotNull(
+                    variant.flavorName,
+                    *variant.productFlavors.map { it.second }.toTypedArray()
                 )
+                    .filter { it != variant.name && it.isNotBlank() }
+                    .toSet()
+                    .forEach {
+                        maybeCreateGenerateTask<Task>(
+                            project = project,
+                            variantName = it,
+                            lastTaskProvider = lastTaskProvider
+                        )
+                    }
             }
 
             // Generate the main global tasks `generateBaselineProfile
@@ -474,69 +468,13 @@
 
     private fun createConfigurationForVariant(
         variant: Variant,
-        mainConfiguration: Configuration?,
-        hasDirectConfiguration: Boolean
-    ): Configuration {
-
-        val variantName = variant.name
-        val productFlavors = variant.productFlavors
-        val flavorName = variant.flavorName ?: ""
-        val buildTypeName = variant.buildType ?: ""
-
-        val buildTypeConfiguration =
-            if (buildTypeName.isNotBlank() && buildTypeName != variantName) {
-                configurationManager.maybeCreate(
-                    nameParts = listOf(buildTypeName, CONFIGURATION_NAME_BASELINE_PROFILES),
-                    canBeResolved = true,
-                    canBeConsumed = false,
-                    buildType = null,
-                    productFlavors = null,
-                    extendFromConfigurations = listOfNotNull(mainConfiguration)
-                )
-            } else null
-
-        val flavorConfiguration =
-            if (flavorName.isNotBlank() && flavorName != variantName) {
-                configurationManager.maybeCreate(
-                    nameParts = listOf(flavorName, CONFIGURATION_NAME_BASELINE_PROFILES),
-                    canBeResolved = true,
-                    canBeConsumed = false,
-                    buildType = null,
-                    productFlavors = null,
-                    extendFromConfigurations = listOfNotNull(mainConfiguration)
-                )
-            } else null
-
-        // When there is direct configuration for the dependency the matching through attributes
-        // is bypassed, because most likely the user meant to match a configuration that does not
-        // have the same tags (for example to a different flavor or build type).
-
-        return if (hasDirectConfiguration) {
-            configurationManager.maybeCreate(
-                nameParts = listOf(variantName, CONFIGURATION_NAME_BASELINE_PROFILES),
-                canBeResolved = true,
-                canBeConsumed = false,
-                extendFromConfigurations = listOfNotNull(
-                    mainConfiguration,
-                    flavorConfiguration,
-                    buildTypeConfiguration
-                ),
-                buildType = null,
-                productFlavors = null
-            )
-        } else {
-            configurationManager.maybeCreate(
-                nameParts = listOf(variantName, CONFIGURATION_NAME_BASELINE_PROFILES),
-                canBeResolved = true,
-                canBeConsumed = false,
-                extendFromConfigurations = listOfNotNull(
-                    mainConfiguration,
-                    flavorConfiguration,
-                    buildTypeConfiguration
-                ),
-                buildType = buildTypeName,
-                productFlavors = productFlavors
-            )
-        }
-    }
+        mainConfiguration: Configuration
+    ) = configurationManager.maybeCreate(
+        nameParts = listOf(variant.name, CONFIGURATION_NAME_BASELINE_PROFILES),
+        canBeResolved = true,
+        canBeConsumed = false,
+        extendFromConfigurations = listOf(mainConfiguration),
+        buildType = variant.buildType ?: "",
+        productFlavors = variant.productFlavors
+    )
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt
index eb7b035..73434be 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt
@@ -49,7 +49,7 @@
         val filterRules: List<Pair<RuleType, String>>
             get() = getMergedListForVariant(variant) { filters.rules }
 
-        val dependencies: List<Pair<Project, String?>>
+        val dependencies: List<Project>
             get() = getMergedListForVariant(variant) { dependencies }
 
         val baselineProfileRulesRewrite: Boolean?
@@ -74,7 +74,13 @@
             variant: Variant,
             getter: BaselineProfileVariantConfigurationImpl.() -> List<T>
         ): List<T> {
-            return listOfNotNull("main", variant.flavorName, variant.buildType, variant.name)
+            return listOfNotNull(
+                "main",
+                variant.flavorName,
+                *variant.productFlavors.map { it.second }.toTypedArray(),
+                variant.buildType,
+                variant.name
+            )
                 .mapNotNull { ext.variants.findByName(it) }
                 .map { getter.invoke(it) }
                 .flatten()
@@ -91,6 +97,7 @@
 
             val definedProperties = listOfNotNull(
                 variant.name,
+                *variant.productFlavors.map { it.second }.toTypedArray(),
                 variant.flavorName,
                 variant.buildType,
                 "main"
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index cb5582f..b3886e4 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -151,10 +151,10 @@
             throw GradleException(
                 """
                 The baseline profile consumer plugin is applied to this module but no dependency
-                has been set. Please review the configuration of build.gradle for the module
-                `${project.path}` making sure that a `baselineProfile` dependency exists and
-                points to a valid `com.android.test` module that has the `androidx.baselineprofile`
-                or `androidx.baselineprofile.producer` plugin applied.
+                has been set. Please review the configuration of build.gradle for this module
+                making sure that a `baselineProfile` dependency exists and points to a valid
+                `com.android.test` module that has the `androidx.baselineprofile` or
+                `androidx.baselineprofile.producer` plugin applied.
                 """.trimIndent()
             )
         }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 985e914..d4d6c81 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -28,6 +28,7 @@
 import androidx.baselineprofile.gradle.utils.build
 import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
 import androidx.baselineprofile.gradle.utils.buildAndFailAndAssertThatOutput
+import androidx.baselineprofile.gradle.utils.camelCase
 import androidx.baselineprofile.gradle.utils.require
 import androidx.baselineprofile.gradle.utils.requireInOrder
 import com.google.common.truth.Truth.assertThat
@@ -794,49 +795,6 @@
     }
 
     @Test
-    fun testVariantDependenciesWithVariantsAndDirectConfiguration() {
-        projectSetup.producer.setupWithFreeAndPaidFlavors(
-            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
-            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
-        )
-
-        // In this setup no dependency is being added through the dependency block.
-        // Instead dependencies are being added through per-variant configuration block.
-        projectSetup.consumer.setup(
-            androidPlugin = ANDROID_APPLICATION_PLUGIN,
-            flavors = true,
-            dependencyOnProducerProject = false,
-            baselineProfileBlock = """
-                variants {
-                    freeRelease {
-                        from(project(":${projectSetup.producer.name}"))
-                    }
-                    paidRelease {
-                        from(project(":${projectSetup.producer.name}"), "freeRelease")
-                    }
-                }
-
-            """.trimIndent()
-        )
-        gradleRunner
-            .withArguments("generateReleaseBaselineProfile", "--stacktrace")
-            .build()
-
-        assertThat(readBaselineProfileFileContent("freeRelease"))
-            .containsExactly(
-                Fixtures.CLASS_1,
-                Fixtures.CLASS_1_METHOD_1,
-            )
-
-        // This output should be the same of free release
-        assertThat(readBaselineProfileFileContent("paidRelease"))
-            .containsExactly(
-                Fixtures.CLASS_1,
-                Fixtures.CLASS_1_METHOD_1,
-            )
-    }
-
-    @Test
     fun testPartialResults() {
         projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN
@@ -978,6 +936,76 @@
             assertThat(notFound).isEmpty()
         }
     }
+
+    @Test
+    fun testMultidimensionalFlavorsAndMatchingFallbacks() {
+        projectSetup.consumer.setupWithBlocks(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN,
+            flavorsBlock = """
+                flavorDimensions = ["tier", "color"]
+                free { dimension "tier" }
+                red { dimension "color" }
+                paid {
+                    dimension "tier"
+                    matchingFallbacks += "free"
+                }
+                blue {
+                    dimension "color"
+                    matchingFallbacks += "red"
+                }
+            """.trimIndent(),
+            buildTypesBlock = "",
+            dependencyOnProducerProject = false,
+            baselineProfileBlock = """
+                variants {
+                    free { from(project(":${projectSetup.producer.name}")) }
+                    red { from(project(":${projectSetup.producer.name}")) }
+                    paid { from(project(":${projectSetup.producer.name}")) }
+                    // blue is already covered by the intersection of the other dimensions so no
+                    // need to specify it.
+                }
+
+            """.trimIndent()
+        )
+        projectSetup.producer.setup(
+            variantProfiles = listOf(
+                VariantProfile(
+                    flavorDimensions = mapOf(
+                        "tier" to "free",
+                        "color" to "red",
+                    ),
+                    buildType = "release",
+                    profileFileLines = mapOf(
+                        "some-test-output" to listOf(
+                            Fixtures.CLASS_1_METHOD_1,
+                            Fixtures.CLASS_1
+                        )
+                    ),
+                    startupFileLines = mapOf(
+                        "some-startup-test-output" to listOf(
+                            Fixtures.CLASS_1_METHOD_1,
+                            Fixtures.CLASS_1
+                        )
+                    ),
+                )
+            )
+        )
+
+        arrayOf(
+            "freeRedRelease",
+            "freeBlueRelease",
+            "paidRedRelease",
+            "paidBlueRelease"
+        ).forEach { variantName ->
+            gradleRunner.build(camelCase("generate", variantName, "baselineProfile")) {
+                assertThat(readBaselineProfileFileContent(variantName))
+                    .containsExactly(
+                        Fixtures.CLASS_1_METHOD_1,
+                        Fixtures.CLASS_1
+                    )
+            }
+        }
+    }
 }
 
 @RunWith(JUnit4::class)
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt
index c4385c0..38526a6 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt
@@ -186,12 +186,29 @@
 }
 
 data class VariantProfile(
-    val flavor: String?,
-    val buildType: String = "release",
-    val profileFileLines: Map<String, List<String>> = mapOf(),
-    val startupFileLines: Map<String, List<String>> = mapOf()
+    val flavorDimensions: Map<String, String>,
+    val buildType: String,
+    val profileFileLines: Map<String, List<String>>,
+    val startupFileLines: Map<String, List<String>>
 ) {
-    val nonMinifiedVariant = "${flavor ?: ""}NonMinified${buildType.capitalized()}"
+
+    val nonMinifiedVariant = camelCase(
+        *flavorDimensions.map { it.value }.toTypedArray(),
+        "nonMinified",
+        buildType
+    )
+
+    constructor(
+        flavor: String?,
+        buildType: String = "release",
+        profileFileLines: Map<String, List<String>> = mapOf(),
+        startupFileLines: Map<String, List<String>> = mapOf()
+    ) : this(
+        flavorDimensions = if (flavor != null) mapOf("version" to flavor) else mapOf(),
+        buildType = buildType,
+        profileFileLines = profileFileLines,
+        startupFileLines = startupFileLines
+    )
 }
 
 interface Module {
@@ -362,14 +379,21 @@
             }
         """.trimIndent()
 
+        val flavors = variantProfiles.flatMap { it.flavorDimensions.toList() }
+        val flavorDimensionNames = flavors
+            .map { it.first }
+            .toSet()
+            .joinToString { """ "$it"""" }
+        val flavorBlocks = flavors
+            .groupBy { it.second }
+            .toList()
+            .map { it.second }
+            .flatten()
+            .joinToString("\n") { """ ${it.second} { dimension "${it.first}" } """ }
         val flavorsBlock = """
             productFlavors {
-                flavorDimensions = ["version"]
-                ${
-            variantProfiles
-                .filter { !it.flavor.isNullOrBlank() }
-                .joinToString("\n") { " ${it.flavor} { dimension \"version\" } " }
-        }
+                flavorDimensions = [$flavorDimensionNames]
+                $flavorBlocks
             }
         """.trimIndent()
 
@@ -511,23 +535,31 @@
         addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN,
         baselineProfileBlock: String = "",
         additionalGradleCodeBlock: String = "",
-    ) {
-        val flavorsBlock = """
-            productFlavors {
+    ) = setupWithBlocks(
+        androidPlugin = androidPlugin,
+        flavorsBlock = if (flavors) """
                 flavorDimensions = ["version"]
                 free { dimension "version" }
                 paid { dimension "version" }
-            }
-
-        """.trimIndent()
-
-        val buildTypeAnotherReleaseBlock = """
-            buildTypes {
+            """.trimIndent() else "",
+        dependencyOnProducerProject = dependencyOnProducerProject,
+        buildTypesBlock = if (buildTypeAnotherRelease) """
                 anotherRelease { initWith(release) }
-            }
+        """.trimIndent() else "",
+        addAppTargetPlugin = addAppTargetPlugin,
+        baselineProfileBlock = baselineProfileBlock,
+        additionalGradleCodeBlock = additionalGradleCodeBlock
+    )
 
-        """.trimIndent()
-
+    fun setupWithBlocks(
+        androidPlugin: String,
+        flavorsBlock: String,
+        buildTypesBlock: String,
+        dependencyOnProducerProject: Boolean = true,
+        addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN,
+        baselineProfileBlock: String = "",
+        additionalGradleCodeBlock: String = "",
+    ) {
         val dependencyOnProducerProjectBlock = """
             dependencies {
                 baselineProfile(project(":$producerName"))
@@ -544,8 +576,20 @@
                 }
                 android {
                     namespace 'com.example.namespace'
-                    ${if (flavors) flavorsBlock else ""}
-                    ${if (buildTypeAnotherRelease) buildTypeAnotherReleaseBlock else ""}
+                    ${
+                """
+                    productFlavors {
+                        $flavorsBlock
+                    }
+                    """.trimIndent()
+            }
+                    ${
+                """
+                    buildTypes {
+                        $buildTypesBlock
+                    }
+                    """.trimIndent()
+            }
                 }
 
                ${if (dependencyOnProducerProject) dependencyOnProducerProjectBlock else ""}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
index 50c69a9..06bb118 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
@@ -18,6 +18,7 @@
 
 import com.google.common.truth.StringSubject
 import com.google.common.truth.Truth.assertThat
+import org.gradle.configurationcache.extensions.capitalized
 import org.gradle.testkit.runner.GradleRunner
 
 internal val GRADLE_CODE_PRINT_TASK = """
@@ -95,3 +96,14 @@
     }
     return remaining
 }
+
+fun camelCase(vararg strings: String): String {
+    if (strings.isEmpty()) return ""
+    return StringBuilder().apply {
+        var shouldCapitalize = false
+        for (str in strings.filter { it.isNotBlank() }) {
+            append(if (shouldCapitalize) str.capitalized() else str)
+            shouldCapitalize = true
+        }
+    }.toString()
+}
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle b/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
index d8f0efa..893b521 100644
--- a/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
+++ b/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
@@ -30,7 +30,7 @@
         }
     }
     productFlavors {
-        flavorDimensions = ["version"]
+        flavorDimensions = ["version", "color"]
         free {
             dimension "version"
             applicationIdSuffix ".free"
@@ -41,6 +41,15 @@
             applicationIdSuffix ".paid"
             versionNameSuffix "-paid"
         }
+        red {
+            dimension "color"
+        }
+        blue {
+            dimension "color"
+            // Note that the producer does not have a `red` flavor dimension to test matching
+            // fallbacks with baseline profiles.
+            matchingFallbacks += "red"
+        }
     }
     namespace "androidx.benchmark.integration.baselineprofile.flavors.consumer"
 }
@@ -52,20 +61,14 @@
 
 baselineProfile {
     filter {
-        include "androidx.benchmark.integration.baselineprofile.flavors.consumer.*"
+        include "androidx.benchmark.integration.baselineprofile.flavors.consumer.**"
     }
     variants {
-        freeRelease {
-            filter {
-                include "androidx.benchmark.integration.baselineprofile.flavors.consumer.free.*"
-                from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
-            }
+        free {
+            from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
         }
-        paidRelease {
-            filter {
-                include "androidx.benchmark.integration.baselineprofile.flavors.consumer.paid.*"
-                from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
-            }
+        paid {
+            from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
         }
     }
 
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRelease/generated/baselineProfiles/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeBlueRelease/generated/baselineProfiles/expected-baseline-prof.txt
similarity index 100%
rename from benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRelease/generated/baselineProfiles/expected-baseline-prof.txt
rename to benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeBlueRelease/generated/baselineProfiles/expected-baseline-prof.txt
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRelease/generated/baselineProfiles/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRedRelease/generated/baselineProfiles/expected-baseline-prof.txt
similarity index 100%
copy from benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRelease/generated/baselineProfiles/expected-baseline-prof.txt
copy to benchmark/integration-tests/baselineprofile-flavors-consumer/src/freeRedRelease/generated/baselineProfiles/expected-baseline-prof.txt
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRelease/generated/baselineProfiles/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidBlueRelease/generated/baselineProfiles/expected-baseline-prof.txt
similarity index 100%
copy from benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRelease/generated/baselineProfiles/expected-baseline-prof.txt
copy to benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidBlueRelease/generated/baselineProfiles/expected-baseline-prof.txt
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRelease/generated/baselineProfiles/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRedRelease/generated/baselineProfiles/expected-baseline-prof.txt
similarity index 100%
rename from benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRelease/generated/baselineProfiles/expected-baseline-prof.txt
rename to benchmark/integration-tests/baselineprofile-flavors-consumer/src/paidRedRelease/generated/baselineProfiles/expected-baseline-prof.txt
diff --git a/benchmark/integration-tests/baselineprofile-flavors-producer/build.gradle b/benchmark/integration-tests/baselineprofile-flavors-producer/build.gradle
index 05d1c99..dcec54e 100644
--- a/benchmark/integration-tests/baselineprofile-flavors-producer/build.gradle
+++ b/benchmark/integration-tests/baselineprofile-flavors-producer/build.gradle
@@ -36,9 +36,12 @@
         }
     }
     productFlavors {
-        flavorDimensions = ["version"]
+        flavorDimensions = ["version", "color"]
         free { dimension "version" }
         paid { dimension "version" }
+        red { dimension "color" }
+        // Note that the consumer has an additional `blue` flavor for dimension `color`. This is
+        // not in this producer to test baseline profile matching fallback.
     }
     targetProjectPath = ":benchmark:integration-tests:baselineprofile-flavors-consumer"
     namespace "androidx.benchmark.integration.baselineprofile.flavors.producer"
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServiceCharacteristicsAdapter.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServiceCharacteristicsAdapter.kt
new file mode 100644
index 0000000..d007847
--- /dev/null
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServiceCharacteristicsAdapter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.bluetooth.integration.testapp.ui.scanner
+
+// TODO(ofy) Migrate to androidx.bluetooth.BluetoothGattCharacteristic once in place
+import android.bluetooth.BluetoothGattCharacteristic
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.bluetooth.integration.testapp.R
+import androidx.recyclerview.widget.RecyclerView
+
+class DeviceServiceCharacteristicsAdapter(
+    private val characteristics: List<BluetoothGattCharacteristic>
+) :
+    RecyclerView.Adapter<DeviceServiceCharacteristicsAdapter.ViewHolder>() {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_device_service, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun getItemCount(): Int {
+        return characteristics.size
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val characteristic = characteristics[position]
+        holder.bind(characteristic)
+    }
+
+    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+        private val textViewUuid: TextView = itemView.findViewById(R.id.text_view_uuid)
+        private val textViewProperties: TextView = itemView.findViewById(R.id.text_view_properties)
+
+        fun bind(characteristic: BluetoothGattCharacteristic) {
+            textViewUuid.text = characteristic.uuid.toString()
+            /*
+                TODO(ofy) Display property type correctly
+                int	PROPERTY_BROADCAST
+                int	PROPERTY_EXTENDED_PROPS
+                int	PROPERTY_INDICATE
+                int	PROPERTY_NOTIFY
+                int	PROPERTY_READ
+                int	PROPERTY_SIGNED_WRITE
+                int	PROPERTY_WRITE
+                int	PROPERTY_WRITE_NO_RESPONSE
+
+                textViewProperties.text = characteristic.properties
+             */
+        }
+    }
+}
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServicesAdapter.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServicesAdapter.kt
index 8e5ab73..3cd26ea 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServicesAdapter.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/DeviceServicesAdapter.kt
@@ -23,6 +23,8 @@
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.bluetooth.integration.testapp.R
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 
 class DeviceServicesAdapter(var services: List<BluetoothGattService>) :
@@ -43,13 +45,21 @@
         holder.bind(service)
     }
 
-    inner class ViewHolder(itemView: View) :
-        RecyclerView.ViewHolder(itemView) {
+    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
 
         private val textViewUuid: TextView = itemView.findViewById(R.id.text_view_uuid)
 
+        private val recyclerViewServiceCharacteristic: RecyclerView =
+            itemView.findViewById(R.id.recycler_view_service_characteristic)
+
         fun bind(service: BluetoothGattService) {
             textViewUuid.text = service.uuid.toString()
+
+            recyclerViewServiceCharacteristic.adapter =
+                DeviceServiceCharacteristicsAdapter(service.characteristics)
+            recyclerViewServiceCharacteristic.addItemDecoration(
+                DividerItemDecoration(itemView.context, LinearLayoutManager.VERTICAL)
+            )
         }
     }
 }
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service.xml
index cb091804..a2187b3 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service.xml
@@ -15,6 +15,7 @@
   limitations under the License.
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -27,11 +28,26 @@
         android:text="@string/generic_attribute"
         android:textColor="#000000" />
 
-    <TextView
-        android:id="@+id/text_view_uuid"
-        android:layout_width="match_parent"
+    <LinearLayout
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        tools:text="UUID: 0x1800" />
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/uuid"
+            android:textAllCaps="true" />
+
+        <TextView
+            android:id="@+id/text_view_uuid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:textColor="@color/black"
+            tools:text="0x1800" />
+
+    </LinearLayout>
 
     <TextView
         android:layout_width="match_parent"
@@ -39,4 +55,13 @@
         android:text="@string/primary_service"
         android:textAllCaps="true" />
 
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view_service_characteristic"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        app:layoutManager="LinearLayoutManager"
+        tools:itemCount="3"
+        tools:listitem="@layout/item_device_service_characteristic" />
+
 </LinearLayout>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service_characteristic.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service_characteristic.xml
new file mode 100644
index 0000000..c07f759
--- /dev/null
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/item_device_service_characteristic.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="8dp">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/uuid"
+            android:textAllCaps="true" />
+
+        <TextView
+            android:id="@+id/text_view_uuid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:textColor="@color/black"
+            tools:text="0x1800" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/properties" />
+
+        <TextView
+            android:id="@+id/text_view_properties"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:textAllCaps="true"
+            android:textColor="@color/black"
+            tools:text="@string/read" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
index cab9c9f..06c5bab 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
@@ -33,7 +33,12 @@
     <string name="connected">Connected</string>
     <string name="connection_failed">Connection Failed</string>
     <string name="generic_attribute">Generic Attribute</string>
+    <string name="uuid">uuid</string>
+    <string name="properties">Properties</string>
     <string name="primary_service">Primary Service</string>
+    <string name="indicate">Indicate</string>
+    <string name="read">Read</string>
+    <string name="write">Write</string>
 
     <!-- Advertiser -->
     <string name="configure_advertising_packet">Configure Advertising Packet</string>
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 0e45231..023909d 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -93,6 +93,15 @@
   fi
 fi
 
+function checkForLeftoverKotlinSessions() {
+  KOTLIN_SESSIONS_DIR=$OUT_DIR/gradle-project-cache/kotlin/sessions
+  NUM_KOTLIN_SESSIONS="$(ls $KOTLIN_SESSIONS_DIR 2>/dev/null | wc -l)"
+  if [ "$NUM_KOTLIN_SESSIONS" -gt 0 ]; then
+    echo "Found $NUM_KOTLIN_SESSIONS leftover kotlin sessions in $KOTLIN_SESSIONS_DIR"
+  fi
+}
+checkForLeftoverKotlinSessions
+
 # run the build
 if run ./gradlew --ci "$@"; then
   echo build passed
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 a84d164..56f1450 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
@@ -131,7 +131,9 @@
         // TODO(b/262772650): camera-pipe support for concurrent camera
         val targetSurfaceCombinations = getSurfaceCombinationsByCameraMode(cameraMode)
         for (surfaceCombination in targetSurfaceCombinations) {
-            if (surfaceCombination.isSupported(surfaceConfigList)) {
+            if (surfaceCombination
+                    .getOrderedSupportedSurfaceConfigList(surfaceConfigList) != null
+            ) {
                 return true
             }
         }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
index 71ea477..df03c5d 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
@@ -107,7 +107,7 @@
 
     companion object {
         fun isImmediateSurfaceReleaseAllowed(): Boolean {
-            return Build.BRAND == "google"
+            return Build.BRAND == "google" && Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1
         }
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
index 88b54d5..e48b3fb 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
@@ -66,9 +66,9 @@
             // Checks the combination is supported by the list retrieved from the
             // ExtraSupportedSurfaceCombinationsContainer.
             for (extraSurfaceCombination in extraSurfaceCombinations) {
-                if (extraSurfaceCombination.isSupported(
+                if (extraSurfaceCombination.getOrderedSupportedSurfaceConfigList(
                         expectedSupportedSurfaceCombination.surfaceConfigList
-                    )
+                    ) != null
                 ) {
                     isSupported = true
                     break
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
index cfded9e..36a2bec 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
@@ -316,7 +316,11 @@
         )
 
         extraConfigurationQuirk.get(cameraId, hardwareLevel).forEach { surfaceCombination ->
-            if (surfaceCombination.isSupported(surfaceCombinationYuvPrivYuv.surfaceConfigList)) {
+            if (surfaceCombination.getOrderedSupportedSurfaceConfigList(
+                    surfaceCombinationYuvPrivYuv.surfaceConfigList
+                )
+                != null
+            ) {
                 return true
             }
         }
@@ -351,7 +355,10 @@
         )
 
         extraConfigurationQuirk.get(cameraId, hardwareLevel).forEach { surfaceCombination ->
-            if (surfaceCombination.isSupported(surfaceCombinationYuvYuvYuv.surfaceConfigList)) {
+            if (surfaceCombination.getOrderedSupportedSurfaceConfigList(
+                    surfaceCombinationYuvYuvYuv.surfaceConfigList
+                ) != null
+            ) {
                 return true
             }
         }
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 1213e14..d18bd1b 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
@@ -17,6 +17,8 @@
 package androidx.camera.camera2.internal;
 
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.os.Build;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -691,124 +693,140 @@
     /**
      * Returns the entire supported stream combinations for devices with Stream Use Case capability
      */
+    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
     @NonNull
     public static List<SurfaceCombination> getStreamUseCaseSupportedCombinationList() {
         List<SurfaceCombination> combinationList = new ArrayList<>();
 
-        // (PRIV, s1440p)
+        // (PRIV, s1440p, PREVIEW_VIDEO_STILL)
         SurfaceCombination surfaceCombination1 = new SurfaceCombination();
         surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL));
         combinationList.add(surfaceCombination1);
 
-        // (YUV, s1440p)
+        // (YUV, s1440p, PREVIEW_VIDEO_STILL)
         SurfaceCombination surfaceCombination2 = new SurfaceCombination();
         surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL));
         combinationList.add(surfaceCombination2);
 
-        // (PRIV, RECORD)
+        // (PRIV, RECORD, VIDEO_RECORD)
         SurfaceCombination surfaceCombination3 = new SurfaceCombination();
         surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         combinationList.add(surfaceCombination3);
 
-        // (YUV, RECORD)
+        // (YUV, RECORD, VIDEO_RECORD)
         SurfaceCombination surfaceCombination4 = new SurfaceCombination();
         surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         combinationList.add(surfaceCombination4);
 
-        // (JPEG, MAXIMUM)
+        // (JPEG, MAXIMUM, STILL_CAPTURE)
         SurfaceCombination surfaceCombination5 = new SurfaceCombination();
         surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination5);
 
-        // (YUV, MAXIMUM)
+        // (YUV, MAXIMUM, STILL_CAPTURE)
         SurfaceCombination surfaceCombination6 = new SurfaceCombination();
         surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination6);
 
-        // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+        // (PRIV, PREVIEW, PREVIEW) + (JPEG, MAXIMUM, STILL_CAPTURE)
         SurfaceCombination surfaceCombination7 = new SurfaceCombination();
         surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination7);
 
-        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+        // (PRIV, PREVIEW, PREVIEW) + (YUV, MAXIMUM, STILL_CAPTURE)
         SurfaceCombination surfaceCombination8 = new SurfaceCombination();
         surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination8);
 
-        // (PRIV, PREVIEW) + (PRIV, RECORD)
+        // (PRIV, PREVIEW, PREVIEW) + (PRIV, RECORD, VIDEO_RECORD)
         SurfaceCombination surfaceCombination9 = new SurfaceCombination();
         surfaceCombination9.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination9.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         combinationList.add(surfaceCombination9);
 
-        // (PRIV, PREVIEW) + (YUV, RECORD)
+        // (PRIV, PREVIEW, PREVIEW) + (YUV, RECORD, VIDEO_RECORD)
         SurfaceCombination surfaceCombination10 = new SurfaceCombination();
         surfaceCombination10.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination10.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         combinationList.add(surfaceCombination10);
 
-        // (PRIV, PREVIEW) + (YUV, PREVIEW)
+        // (PRIV, PREVIEW, PREVIEW) + (YUV, PREVIEW, PREVIEW)
         SurfaceCombination surfaceCombination11 = new SurfaceCombination();
         surfaceCombination11.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination11.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         combinationList.add(surfaceCombination11);
 
-        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+        // (PRIV, PREVIEW, PREVIEW) + (PRIV, RECORD, VIDEO_RECORD) + (JPEG, RECORD, STILL_CAPTURE)
         SurfaceCombination surfaceCombination12 = new SurfaceCombination();
         surfaceCombination12.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination12.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD,
+                        CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD));
         surfaceCombination12.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination12);
 
-        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+        // (PRIV, PREVIEW, PREVIEW) + (YUV, RECORD, VIDEO_RECORD) + (JPEG, RECORD, STILL_CAPTURE)
         SurfaceCombination surfaceCombination13 = new SurfaceCombination();
         surfaceCombination13.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination13.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD,
+                        CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD));
         surfaceCombination13.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination13);
 
-        // (PRIV, PREVIEW) + (YUV, RECORD) + (JPEG, RECORD)
+        // (PRIV, PREVIEW, PREVIEW) + (YUV, PREVIEW, PREVIEW) + (JPEG, MAXIMUM, STILL_CAPTURE)
         SurfaceCombination surfaceCombination14 = new SurfaceCombination();
         surfaceCombination14.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination14.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
         surfaceCombination14.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination14);
-
-        // (PRIV, PREVIEW) + (YUV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination15 = new SurfaceCombination();
-        surfaceCombination15.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination15.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination15.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM,
+                        CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
         combinationList.add(surfaceCombination14);
 
         return combinationList;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
index 82bfaf2..f0b742e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
@@ -33,7 +33,6 @@
 import androidx.camera.core.ImageAnalysis;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Preview;
-import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.AttachedSurfaceInfo;
 import androidx.camera.core.impl.CameraMode;
 import androidx.camera.core.impl.Config;
@@ -42,6 +41,7 @@
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.StreamSpec;
+import androidx.camera.core.impl.SurfaceConfig;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.core.streamsharing.StreamSharing;
@@ -64,12 +64,42 @@
     public static final Config.Option<Long> STREAM_USE_CASE_STREAM_SPEC_OPTION =
             Config.Option.create("camera2.streamSpec.streamUseCase", long.class);
 
-    private StreamUseCaseUtil() {
-
-    }
+    private static final Map<Long, Set<UseCaseConfigFactory.CaptureType>>
+            sStreamUseCaseToEligibleCaptureTypesMap = new HashMap<>();
 
     private static Map<Class<?>, Long> sUseCaseToStreamUseCaseMapping;
 
+    static {
+        if (Build.VERSION.SDK_INT >= 33) {
+            Set<UseCaseConfigFactory.CaptureType> captureTypes = new HashSet<>();
+            captureTypes.add(UseCaseConfigFactory.CaptureType.PREVIEW);
+            sStreamUseCaseToEligibleCaptureTypesMap.put(
+                    Long.valueOf(
+                            CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL),
+                    captureTypes);
+            captureTypes = new HashSet<>();
+            captureTypes.add(UseCaseConfigFactory.CaptureType.PREVIEW);
+            captureTypes.add(UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS);
+            sStreamUseCaseToEligibleCaptureTypesMap.put(
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW),
+                    captureTypes);
+            captureTypes = new HashSet<>();
+            captureTypes.add(UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE);
+            sStreamUseCaseToEligibleCaptureTypesMap.put(
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE),
+                    captureTypes);
+            captureTypes = new HashSet<>();
+            captureTypes.add(UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE);
+            sStreamUseCaseToEligibleCaptureTypesMap.put(
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD),
+                    captureTypes);
+            // TODO(b/200306659): 280335572 Handle StreamSharing
+        }
+    }
+
+    private StreamUseCaseUtil() {
+    }
+
     /**
      * Populates the mapping between surfaces of a capture session and the Stream Use Case of their
      * associated stream.
@@ -231,8 +261,8 @@
     /**
      * Return true if the given feature settings is appropriate for stream use case usage.
      */
-    public static boolean shouldUseStreamUseCase(@NonNull
-            SupportedSurfaceCombination.FeatureSettings featureSettings) {
+    public static boolean shouldUseStreamUseCase(
+            @NonNull SupportedSurfaceCombination.FeatureSettings featureSettings) {
         return featureSettings.getCameraMode() == CameraMode.DEFAULT
                 && featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_8_BIT;
     }
@@ -270,9 +300,12 @@
             Preconditions.checkNotNull(Preconditions.checkNotNull(
                     suggestedStreamSpecMap.get(useCaseConfig)).getImplementationOptions());
         }
-        Set<Long> availableStreamUseCaseSet = new HashSet<>();
         long[] availableStreamUseCases = characteristicsCompat.get(
                 CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
+        if (availableStreamUseCases == null || availableStreamUseCases.length == 0) {
+            return false;
+        }
+        Set<Long> availableStreamUseCaseSet = new HashSet<>();
         for (Long availableStreamUseCase : availableStreamUseCases) {
             availableStreamUseCaseSet.add(availableStreamUseCase);
         }
@@ -286,16 +319,8 @@
                                 oldImplementationOptions.retrieveOption(
                                         Camera2ImplConfig.STREAM_USE_CASE_OPTION));
                 if (newImplementationOptions != null) {
-                    StreamSpec.Builder newStreamSpecBuilder =
-                            StreamSpec.builder(attachedSurfaceInfo.getSize())
-                                    .setDynamicRange(attachedSurfaceInfo.getDynamicRange())
-                                    .setImplementationOptions(newImplementationOptions);
-                    if (attachedSurfaceInfo.getTargetFrameRate() != null) {
-                        newStreamSpecBuilder.setExpectedFrameRateRange(
-                                attachedSurfaceInfo.getTargetFrameRate());
-                    }
                     attachedSurfaceStreamSpecMap.put(attachedSurfaceInfo,
-                            newStreamSpecBuilder.build());
+                            attachedSurfaceInfo.toStreamSpec(newImplementationOptions));
                 }
             }
             for (UseCaseConfig<?> newUseCaseConfig : newUseCaseConfigs) {
@@ -318,6 +343,147 @@
     }
 
     /**
+     * Return true if  the stream use cases in the given surface configurations are available for
+     * the device.
+     */
+    public static boolean areStreamUseCasesAvailableForSurfaceConfigs(
+            @NonNull CameraCharacteristicsCompat characteristicsCompat,
+            @NonNull List<SurfaceConfig> surfaceConfigs) {
+        if (Build.VERSION.SDK_INT < 33) {
+            return false;
+        }
+        long[] availableStreamUseCases = characteristicsCompat.get(
+                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
+        if (availableStreamUseCases == null || availableStreamUseCases.length == 0) {
+            return false;
+        }
+        Set<Long> availableStreamUseCaseSet = new HashSet<>();
+        for (Long availableStreamUseCase : availableStreamUseCases) {
+            availableStreamUseCaseSet.add(availableStreamUseCase);
+        }
+        for (SurfaceConfig surfaceConfig : surfaceConfigs) {
+            if (!availableStreamUseCaseSet.contains(surfaceConfig.getStreamUseCase())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return true if the given capture type and stream use case are a eligible pair.
+     */
+    private static boolean isEligibleCaptureType(UseCaseConfigFactory.CaptureType captureType,
+            long streamUseCase) {
+        if (Build.VERSION.SDK_INT < 33) {
+            return false;
+        }
+        return sStreamUseCaseToEligibleCaptureTypesMap.containsKey(streamUseCase)
+                && sStreamUseCaseToEligibleCaptureTypesMap.get(streamUseCase).contains(
+                captureType);
+    }
+
+    /**
+     * Return true if the stream use cases contained in surfaceConfigsWithStreamUseCases all have
+     * eligible capture type pairing with the use cases that these surfaceConfigs are constructed
+     * from.
+     *
+     * @param surfaceConfigIndexAttachedSurfaceInfoMap mapping between an surfaceConfig's index
+     *                                                 in the list and the attachedSurfaceInfo it
+     *                                                 is constructed from
+     * @param surfaceConfigIndexUseCaseConfigMap       mapping between an surfaceConfig's index
+     *      *                                          in the list and the useCaseConfig it is
+     *                                                 constructed from
+     * @param surfaceConfigsWithStreamUseCase          the supported surfaceConfigs that contains
+     *                                                 accurate streamUseCases
+     */
+    public static boolean areCaptureTypesEligible(
+            @NonNull Map<Integer, AttachedSurfaceInfo> surfaceConfigIndexAttachedSurfaceInfoMap,
+            @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigIndexUseCaseConfigMap,
+            @NonNull List<SurfaceConfig> surfaceConfigsWithStreamUseCase) {
+        for (int i = 0; i < surfaceConfigsWithStreamUseCase.size(); i++) {
+            // Verify that the use case has the eligible capture type the given stream use case.
+            long streamUseCase = surfaceConfigsWithStreamUseCase.get(i).getStreamUseCase();
+            if (surfaceConfigIndexAttachedSurfaceInfoMap.containsKey(i)) {
+                AttachedSurfaceInfo attachedSurfaceInfo =
+                        surfaceConfigIndexAttachedSurfaceInfoMap.get(i);
+                if (!isEligibleCaptureType(attachedSurfaceInfo.getCaptureTypes().size() == 1
+                        ? attachedSurfaceInfo.getCaptureTypes().get(0) :
+                        UseCaseConfigFactory.CaptureType.STREAM_SHARING, streamUseCase)) {
+                    return false;
+                }
+            } else if (surfaceConfigIndexUseCaseConfigMap.containsKey(i)) {
+                UseCaseConfig<?> newUseCaseConfig =
+                        surfaceConfigIndexUseCaseConfigMap.get(i);
+                if (!isEligibleCaptureType(newUseCaseConfig.getCaptureType(), streamUseCase)) {
+                    return false;
+                }
+            } else {
+                throw new AssertionError("SurfaceConfig does not map to any use case");
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @param suggestedStreamSpecMap                   mapping between useCaseConfig and its
+     *                                                 streamSpecs
+     * @param attachedSurfaceStreamSpecMap             mapping between attachedSurfaceInfo and its
+     *                                                 streamSpecs that contains streamUseCases.
+     *                                                 All streamSpecs in this map has
+     *                                                 streamUseCases
+     * @param surfaceConfigIndexAttachedSurfaceInfoMap mapping between an surfaceConfig's index
+     *                                                 in the list and the
+     *                                                 attachedSurfaceInfo it
+     *                                                 is constructed from
+     *@param surfaceConfigIndexUseCaseConfigMap        mapping between an surfaceConfig's
+     *                                                 index in the list and the useCaseConfig
+     *                                                 it is constructed from
+     * @param surfaceConfigsWithStreamUseCase          the supported surfaceConfigs that contains
+     *                                                 accurate streamUseCases
+     */
+    public static void populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs(
+            @NonNull Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap,
+            @NonNull Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap,
+            @NonNull Map<Integer, AttachedSurfaceInfo> surfaceConfigIndexAttachedSurfaceInfoMap,
+            @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigIndexUseCaseConfigMap,
+            @NonNull List<SurfaceConfig> surfaceConfigsWithStreamUseCase) {
+        // Populate StreamSpecs with stream use cases.
+        for (int i = 0; i < surfaceConfigsWithStreamUseCase.size(); i++) {
+            long streamUseCase = surfaceConfigsWithStreamUseCase.get(i).getStreamUseCase();
+            if (surfaceConfigIndexAttachedSurfaceInfoMap.containsKey(i)) {
+                AttachedSurfaceInfo attachedSurfaceInfo =
+                        surfaceConfigIndexAttachedSurfaceInfoMap.get(i);
+                Config oldImplementationOptions = attachedSurfaceInfo.getImplementationOptions();
+                Config newImplementationOptions =
+                        getUpdatedImplementationOptionsWithUseCaseStreamSpecOption(
+                                oldImplementationOptions, streamUseCase);
+                if (newImplementationOptions != null) {
+                    attachedSurfaceStreamSpecMap.put(attachedSurfaceInfo,
+                            attachedSurfaceInfo.toStreamSpec(newImplementationOptions));
+                }
+            } else if (surfaceConfigIndexUseCaseConfigMap.containsKey(i)) {
+                UseCaseConfig<?> newUseCaseConfig =
+                        surfaceConfigIndexUseCaseConfigMap.get(i);
+                StreamSpec oldStreamSpec = suggestedStreamSpecMap.get(newUseCaseConfig);
+                Config oldImplementationOptions = oldStreamSpec.getImplementationOptions();
+                Config newImplementationOptions =
+                        getUpdatedImplementationOptionsWithUseCaseStreamSpecOption(
+                                oldImplementationOptions, streamUseCase);
+                if (newImplementationOptions != null) {
+                    StreamSpec newStreamSpec =
+                            oldStreamSpec.toBuilder().setImplementationOptions(
+                                    newImplementationOptions).build();
+                    suggestedStreamSpecMap.put(newUseCaseConfig, newStreamSpec);
+                }
+
+            } else {
+                throw new AssertionError("SurfaceConfig does not map to any use case");
+            }
+        }
+
+    }
+
+    /**
      * Given an old options, return a new option with stream use case stream spec option inserted
      */
     @Nullable
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 bb83079..32ae3eb 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
@@ -216,7 +216,8 @@
 
         for (SurfaceCombination surfaceCombination : getSurfaceCombinationsByFeatureSettings(
                 featureSettings)) {
-            isSupported = surfaceCombination.isSupported(surfaceConfigList);
+            isSupported = surfaceCombination.getOrderedSupportedSurfaceConfigList(surfaceConfigList)
+                    != null;
 
             if (isSupported) {
                 break;
@@ -227,7 +228,7 @@
     }
 
     @Nullable
-    SurfaceCombination getSupportedStreamUseCaseSurfaceCombination(
+    List<SurfaceConfig> getOrderedSupportedStreamUseCaseSurfaceConfigList(
             @NonNull FeatureSettings featureSettings,
             List<SurfaceConfig> surfaceConfigList) {
         if (!StreamUseCaseUtil.shouldUseStreamUseCase(featureSettings)) {
@@ -237,9 +238,12 @@
         for (SurfaceCombination surfaceCombination : mSurfaceCombinationsStreamUseCase) {
             // Stream use case table doesn't support sublist. SurfaceCombination and
             // SurfaceConfig list need to be EXACT match.
-            if (surfaceCombination.getSurfaceConfigList().size() == surfaceConfigList.size()
-                    && surfaceCombination.isSupported(surfaceConfigList)) {
-                return surfaceCombination;
+            if (surfaceCombination.getSurfaceConfigList().size() == surfaceConfigList.size()) {
+                List<SurfaceConfig> orderedSurfaceConfigList =
+                        surfaceCombination.getOrderedSupportedSurfaceConfigList(surfaceConfigList);
+                if (orderedSurfaceConfigList != null) {
+                    return orderedSurfaceConfigList;
+                }
             }
         }
         return null;
@@ -576,14 +580,14 @@
 
         boolean containsZsl = StreamUseCaseUtil.containsZslUseCase(attachedSurfaces,
                 newUseCaseConfigs);
-        SurfaceCombination surfaceCombinationForStreamUseCase =
+        List<SurfaceConfig> orderedSurfaceConfigListForStreamUseCase =
                 mIsStreamUseCaseSupported && !containsZsl
-                        ? getSupportedStreamUseCaseSurfaceCombination(featureSettings,
+                        ? getOrderedSupportedStreamUseCaseSurfaceConfigList(featureSettings,
                         surfaceConfigs) : null;
 
         boolean isSurfaceCombinationSupported = checkSupported(featureSettings, surfaceConfigs);
 
-        if (surfaceCombinationForStreamUseCase == null && !isSurfaceCombinationSupported) {
+        if (orderedSurfaceConfigListForStreamUseCase == null && !isSurfaceCombinationSupported) {
             throw new IllegalArgumentException(
                     "No supported surface combination is found for camera device - Id : "
                             + mCameraId + ".  May be attempting to bind too many use cases. "
@@ -631,30 +635,57 @@
 
         Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
         Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+        // 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
+        // them map streamUseCases in orderedSurfaceConfigListForStreamUseCase, which is in the
+        // same order as surfaceConfigs list, to the original useCases to determine the
+        // captureTypes are correct.
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigIndexAttachedSurfaceInfoMap =
+                new HashMap<>();
+        Map<Integer, UseCaseConfig<?>> surfaceConfigIndexUseCaseConfigMap =
+                new HashMap<>();
 
         List<Size> savedSizes = null;
         int savedConfigMaxFps = Integer.MAX_VALUE;
         List<Size> savedSizesForStreamUseCase = null;
         int savedConfigMaxFpsForStreamUseCase = Integer.MAX_VALUE;
 
-        if (surfaceCombinationForStreamUseCase != null) {
+        if (orderedSurfaceConfigListForStreamUseCase != null) {
             // Check if any possible size arrangement is supported for stream use case.
             for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
-                List<SurfaceConfig> surfaceConfigList = getSurfaceConfigListAndFpsCeiling(
+                surfaceConfigs = getSurfaceConfigListAndFpsCeiling(
                         cameraMode,
                         attachedSurfaces, possibleSizeList, newUseCaseConfigs,
-                        useCasesPriorityOrder, existingSurfaceFrameRateCeiling).first;
-                surfaceCombinationForStreamUseCase =
-                        getSupportedStreamUseCaseSurfaceCombination(featureSettings,
-                                surfaceConfigList);
-                if (surfaceCombinationForStreamUseCase != null) {
-                    break;
+                        useCasesPriorityOrder, existingSurfaceFrameRateCeiling,
+                        surfaceConfigIndexAttachedSurfaceInfoMap,
+                        surfaceConfigIndexUseCaseConfigMap).first;
+                orderedSurfaceConfigListForStreamUseCase =
+                        getOrderedSupportedStreamUseCaseSurfaceConfigList(featureSettings,
+                                surfaceConfigs);
+                if (orderedSurfaceConfigListForStreamUseCase != null
+                        && !StreamUseCaseUtil.areCaptureTypesEligible(
+                        surfaceConfigIndexAttachedSurfaceInfoMap,
+                        surfaceConfigIndexUseCaseConfigMap,
+                        orderedSurfaceConfigListForStreamUseCase)) {
+                    orderedSurfaceConfigListForStreamUseCase = null;
                 }
+                if (orderedSurfaceConfigListForStreamUseCase != null) {
+                    if (StreamUseCaseUtil.areStreamUseCasesAvailableForSurfaceConfigs(
+                            mCharacteristics, orderedSurfaceConfigListForStreamUseCase)) {
+                        break;
+                    } else {
+                        orderedSurfaceConfigListForStreamUseCase = null;
+                    }
+                }
+                surfaceConfigIndexAttachedSurfaceInfoMap.clear();
+                surfaceConfigIndexUseCaseConfigMap.clear();
             }
 
             // We can terminate early if surface combination is not supported and none of the
             // possible size arrangement supports stream use case either.
-            if (surfaceCombinationForStreamUseCase == null && !isSurfaceCombinationSupported) {
+            if (orderedSurfaceConfigListForStreamUseCase == null
+                    && !isSurfaceCombinationSupported) {
                 throw new IllegalArgumentException(
                         "No supported surface combination is found for camera device - Id : "
                                 + mCameraId + ".  May be attempting to bind too many use cases. "
@@ -672,7 +703,7 @@
             Pair<List<SurfaceConfig>, Integer> resultPair =
                     getSurfaceConfigListAndFpsCeiling(cameraMode,
                             attachedSurfaces, possibleSizeList, newUseCaseConfigs,
-                            useCasesPriorityOrder, existingSurfaceFrameRateCeiling);
+                            useCasesPriorityOrder, existingSurfaceFrameRateCeiling, null, null);
             List<SurfaceConfig> surfaceConfigList = resultPair.first;
             int currentConfigFramerateCeiling = resultPair.second;
             boolean isConfigFrameRateAcceptable = true;
@@ -717,8 +748,9 @@
             // use case table, keep an independent tracking on the saved sizes and max FPS. Only
             // use stream use case if the save sizes for the normal case and for stream use case
             // are the same.
-            if (surfaceCombinationForStreamUseCase != null && !supportedSizesForStreamUseCaseFound
-                    && getSupportedStreamUseCaseSurfaceCombination(
+            if (orderedSurfaceConfigListForStreamUseCase != null
+                    && !supportedSizesForStreamUseCaseFound
+                    && getOrderedSupportedStreamUseCaseSurfaceConfigList(
                     featureSettings, surfaceConfigList) != null) {
                 if (savedConfigMaxFpsForStreamUseCase == Integer.MAX_VALUE) {
                     savedConfigMaxFpsForStreamUseCase = currentConfigFramerateCeiling;
@@ -771,7 +803,7 @@
         }
 
         // Only perform stream use case operations if the saved max FPS and sizes are the same
-        if (surfaceCombinationForStreamUseCase != null
+        if (orderedSurfaceConfigListForStreamUseCase != null
                 && savedConfigMaxFps == savedConfigMaxFpsForStreamUseCase
                 && savedSizes.size() == savedSizesForStreamUseCase.size()) {
             boolean hasDifferenceSavedSizes = false;
@@ -787,8 +819,12 @@
                                 mCharacteristics, attachedSurfaces, suggestedStreamSpecMap,
                                 attachedSurfaceStreamSpecMap);
                 if (!hasStreamUseCaseOverride) {
-                    // TODO(b/280335430): Use surfaceCombinationForStreamUseCase to populate the
-                    //  streamSpec maps
+                    StreamUseCaseUtil
+                            .populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs(
+                                    suggestedStreamSpecMap, attachedSurfaceStreamSpecMap,
+                                    surfaceConfigIndexAttachedSurfaceInfoMap,
+                                    surfaceConfigIndexUseCaseConfigMap,
+                                    orderedSurfaceConfigListForStreamUseCase);
                 }
             }
         }
@@ -801,10 +837,16 @@
             List<AttachedSurfaceInfo> attachedSurfaces,
             List<Size> possibleSizeList, List<UseCaseConfig<?>> newUseCaseConfigs,
             List<Integer> useCasesPriorityOrder,
-            int currentConfigFramerateCeiling) {
+            int currentConfigFramerateCeiling,
+            @Nullable Map<Integer, AttachedSurfaceInfo> surfaceConfigIndexAttachedSurfaceInfoMap,
+            @Nullable Map<Integer, UseCaseConfig<?>> surfaceConfigIndexUseCaseConfigMap) {
         List<SurfaceConfig> surfaceConfigList = new ArrayList<>();
         for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
             surfaceConfigList.add(attachedSurfaceInfo.getSurfaceConfig());
+            if (surfaceConfigIndexAttachedSurfaceInfoMap != null) {
+                surfaceConfigIndexAttachedSurfaceInfoMap.put(surfaceConfigList.size() - 1,
+                        attachedSurfaceInfo);
+            }
         }
 
         // Attach SurfaceConfig of new use cases
@@ -814,12 +856,15 @@
                     newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
             int imageFormat = newUseCase.getInputFormat();
             // add new use case/size config to list of surfaces
-            surfaceConfigList.add(
-                    SurfaceConfig.transformSurfaceConfig(
-                            cameraMode,
-                            imageFormat,
-                            size,
-                            getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)));
+            SurfaceConfig surfaceConfig = SurfaceConfig.transformSurfaceConfig(
+                    cameraMode,
+                    imageFormat,
+                    size,
+                    getUpdatedSurfaceSizeDefinitionByFormat(imageFormat));
+            surfaceConfigList.add(surfaceConfig);
+            if (surfaceConfigIndexUseCaseConfigMap != null) {
+                surfaceConfigIndexUseCaseConfigMap.put(surfaceConfigList.size() - 1, newUseCase);
+            }
             // get the maximum fps of the new surface and update the maximum fps of the
             // proposed configuration
             currentConfigFramerateCeiling = getUpdatedMaximumFps(
@@ -1058,8 +1103,10 @@
     }
 
     private void generateStreamUseCaseSupportedCombinationList() {
-        mSurfaceCombinationsStreamUseCase.addAll(
-                GuaranteedConfigurationsUtil.getStreamUseCaseSupportedCombinationList());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            mSurfaceCombinationsStreamUseCase.addAll(
+                    GuaranteedConfigurationsUtil.getStreamUseCaseSupportedCombinationList());
+        }
     }
 
     private void checkCustomization() {
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 7439f09..f2077af 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
@@ -19,6 +19,7 @@
 import static android.os.Build.VERSION.SDK_INT;
 
 import static androidx.camera.camera2.internal.StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION;
+import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
 import static androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
 import static androidx.camera.core.ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG;
 
@@ -341,7 +342,7 @@
     public void shouldUseStreamUseCase_bitDepthNotSupported() {
         assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
                 SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_10_BIT)));
+                        BIT_DEPTH_10_BIT)));
     }
 
     @SdkSuppress(minSdkVersion = 33)
@@ -455,6 +456,197 @@
         );
     }
 
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void areStreamUseCasesAvailableForSurfaceConfigs_success() {
+        List<SurfaceConfig> surfaceConfigList = new ArrayList<>();
+        surfaceConfigList.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        assertTrue(StreamUseCaseUtil.areStreamUseCasesAvailableForSurfaceConfigs(
+                getCameraCharacteristicsCompat(false), surfaceConfigList));
+
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void areStreamUseCasesAvailableForSurfaceConfigs_fail() {
+        List<SurfaceConfig> surfaceConfigList = new ArrayList<>();
+        surfaceConfigList.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        assertFalse(StreamUseCaseUtil.areStreamUseCasesAvailableForSurfaceConfigs(
+                getCameraCharacteristicsCompat(true), surfaceConfigList));
+
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void areCaptureTypesEligible_success() {
+        List<SurfaceConfig> surfaceConfigsWithStreamUseCase = new ArrayList<>();
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+        List<SurfaceConfig> originalSurfaceConfigs = new ArrayList<>();
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW));
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD));
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
+                new HashMap<>();
+        surfaceConfigAttachedSurfaceInfoMap.put(0,
+                getFakeAttachedSurfaceInfo(false, false, false,
+                        UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
+        @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigUseCaseConfigMap =
+                new HashMap<>();
+        surfaceConfigUseCaseConfigMap.put(1,
+                getFakeUseCaseConfigWithOptions(false, false, false,
+                        UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE, ImageFormat.PRIVATE));
+
+        assertTrue(StreamUseCaseUtil.areCaptureTypesEligible(surfaceConfigAttachedSurfaceInfoMap,
+                surfaceConfigUseCaseConfigMap, surfaceConfigsWithStreamUseCase));
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void areCaptureTypesEligible_fail() {
+        List<SurfaceConfig> surfaceConfigsWithStreamUseCase = new ArrayList<>();
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+        List<SurfaceConfig> originalSurfaceConfigs = new ArrayList<>();
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW));
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD));
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
+                new HashMap<>();
+        surfaceConfigAttachedSurfaceInfoMap.put(0,
+                getFakeAttachedSurfaceInfo(false, false, false,
+                        UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
+        @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigUseCaseConfigMap =
+                new HashMap<>();
+        surfaceConfigUseCaseConfigMap.put(1,
+                getFakeUseCaseConfigWithOptions(false, false, false,
+                        UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
+
+        assertFalse(StreamUseCaseUtil.areCaptureTypesEligible(surfaceConfigAttachedSurfaceInfoMap,
+                surfaceConfigUseCaseConfigMap, surfaceConfigsWithStreamUseCase));
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test(expected = AssertionError.class)
+    public void areCaptureTypesEligible_mappingError() {
+        List<SurfaceConfig> surfaceConfigsWithStreamUseCase = new ArrayList<>();
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+        List<SurfaceConfig> originalSurfaceConfigs = new ArrayList<>();
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW));
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD));
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
+                new HashMap<>();
+        @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigUseCaseConfigMap =
+                new HashMap<>();
+        surfaceConfigUseCaseConfigMap.put(1,
+                getFakeUseCaseConfigWithOptions(false, false, false,
+                        UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE, ImageFormat.PRIVATE));
+
+        StreamUseCaseUtil.areCaptureTypesEligible(surfaceConfigAttachedSurfaceInfoMap,
+                surfaceConfigUseCaseConfigMap, surfaceConfigsWithStreamUseCase);
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_success() {
+        List<SurfaceConfig> surfaceConfigsWithStreamUseCase = new ArrayList<>();
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+        List<SurfaceConfig> originalSurfaceConfigs = new ArrayList<>();
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW));
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD));
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
+                new HashMap<>();
+        AttachedSurfaceInfo attachedSurfaceInfo = getFakeAttachedSurfaceInfo(false, false, false,
+                UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE);
+        surfaceConfigAttachedSurfaceInfoMap.put(0, attachedSurfaceInfo);
+        @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigUseCaseConfigMap =
+                new HashMap<>();
+        UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(false, false, false,
+                UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE, ImageFormat.PRIVATE);
+        surfaceConfigUseCaseConfigMap.put(1, useCaseConfig);
+        @NonNull Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap =
+                new HashMap<>();
+        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+        suggestedStreamSpecMap.put(useCaseConfig,
+                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
+
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs(
+                suggestedStreamSpecMap, attachedSurfaceStreamSpecMap,
+                surfaceConfigAttachedSurfaceInfoMap,
+                surfaceConfigUseCaseConfigMap, surfaceConfigsWithStreamUseCase);
+
+        assertTrue(attachedSurfaceStreamSpecMap.get(
+                attachedSurfaceInfo).getImplementationOptions().retrieveOption(
+                STREAM_USE_CASE_STREAM_SPEC_OPTION)
+                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW);
+        assertTrue(suggestedStreamSpecMap.get(
+                useCaseConfig).getImplementationOptions().retrieveOption(
+                STREAM_USE_CASE_STREAM_SPEC_OPTION)
+                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD);
+    }
+
+    @SdkSuppress(minSdkVersion = 33)
+    @Test(expected = AssertionError.class)
+    public void populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_mappingError() {
+        List<SurfaceConfig> surfaceConfigsWithStreamUseCase = new ArrayList<>();
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+        surfaceConfigsWithStreamUseCase.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD,
+                CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+        List<SurfaceConfig> originalSurfaceConfigs = new ArrayList<>();
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.PREVIEW));
+        originalSurfaceConfigs.add(SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV, SurfaceConfig.ConfigSize.RECORD));
+        Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
+                new HashMap<>();
+        @NonNull Map<Integer, UseCaseConfig<?>> surfaceConfigUseCaseConfigMap =
+                new HashMap<>();
+        UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(false, false, false,
+                UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE, ImageFormat.PRIVATE);
+        surfaceConfigUseCaseConfigMap.put(1, useCaseConfig);
+        @NonNull Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap =
+                new HashMap<>();
+        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+        suggestedStreamSpecMap.put(useCaseConfig,
+                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
+
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs(
+                suggestedStreamSpecMap, attachedSurfaceStreamSpecMap,
+                surfaceConfigAttachedSurfaceInfoMap,
+                surfaceConfigUseCaseConfigMap, surfaceConfigsWithStreamUseCase);
+    }
+
     private UseCaseConfig<?> getFakeUseCaseConfigWithOptions(boolean camera2InteropOverride,
             boolean isZslDisabled, boolean isZslCaptureMode,
             UseCaseConfigFactory.CaptureType captureType, int imageFormat) {
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 b0437c1..27a103f 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
@@ -526,7 +526,8 @@
     @Test
     fun checkConcurrentSurfaceCombinationSupportedInConcurrentCameraMode() {
         shadowOf(context.packageManager).setSystemFeature(
-            FEATURE_CAMERA_CONCURRENT, true)
+            FEATURE_CAMERA_CONCURRENT, true
+        )
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
         )
@@ -546,7 +547,8 @@
     @Test
     fun checkConcurrentSurfaceCombinationSubListSupportedInConcurrentCameraMode() {
         shadowOf(context.packageManager).setSystemFeature(
-            FEATURE_CAMERA_CONCURRENT, true)
+            FEATURE_CAMERA_CONCURRENT, true
+        )
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
         )
@@ -554,8 +556,11 @@
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         GuaranteedConfigurationsUtil.getConcurrentSupportedCombinationList().also {
-            assertThat(isAllSubConfigListSupported(
-                CameraMode.CONCURRENT_CAMERA, supportedSurfaceCombination, it)).isTrue()
+            assertThat(
+                isAllSubConfigListSupported(
+                    CameraMode.CONCURRENT_CAMERA, supportedSurfaceCombination, it
+                )
+            ).isTrue()
         }
     }
 
@@ -600,8 +605,11 @@
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         GuaranteedConfigurationsUtil.getUltraHighResolutionSupportedCombinationList().also {
-            assertThat(isAllSubConfigListSupported(
-                CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, supportedSurfaceCombination, it)).isTrue()
+            assertThat(
+                isAllSubConfigListSupported(
+                    CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, supportedSurfaceCombination, it
+                )
+            ).isTrue()
         }
     }
 
@@ -2749,7 +2757,8 @@
     @Test
     fun generateCorrectSurfaceDefinition() {
         shadowOf(context.packageManager).setSystemFeature(
-            FEATURE_CAMERA_CONCURRENT, true)
+            FEATURE_CAMERA_CONCURRENT, true
+        )
         setupCameraAndInitCameraX()
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
@@ -2790,7 +2799,8 @@
     @Test
     fun correctS720pSize_withSmallerOutputSizes() {
         shadowOf(context.packageManager).setSystemFeature(
-            FEATURE_CAMERA_CONCURRENT, true)
+            FEATURE_CAMERA_CONCURRENT, true
+        )
         setupCameraAndInitCameraX(
             supportedSizes = arrayOf(RESOLUTION_VGA)
         )
@@ -2810,7 +2820,8 @@
     @Test
     fun correctS1440pSize_withSmallerOutputSizes() {
         shadowOf(context.packageManager).setSystemFeature(
-            FEATURE_CAMERA_CONCURRENT, true)
+            FEATURE_CAMERA_CONCURRENT, true
+        )
         setupCameraAndInitCameraX(
             supportedSizes = arrayOf(RESOLUTION_VGA)
         )
@@ -3083,6 +3094,107 @@
         ).isFalse()
     }
 
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_differentMaxSize() {
+        val useCase1 =
+            createUseCase(CaptureType.PREVIEW) // VIDEO
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, MAXIMUM_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap, hardwareLevel = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        // In this case, the stream use case path and limited path would produce two different max
+        // sizes, resulting in the stream use case path being dropped.
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_success() {
+        val useCase1 =
+            createUseCase(CaptureType.VIDEO_CAPTURE) // VIDEO
+        val useCase2 =
+            createUseCase(CaptureType.PREVIEW) // PREVIEW
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, RECORD_SIZE)
+            put(useCase2, PREVIEW_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.retrieveOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isEqualTo(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD)
+        assertThat(
+            resultPair.first[useCase2.currentConfig]!!.implementationOptions!!.retrieveOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isEqualTo(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_wrongImageFormat() {
+        val useCase1 =
+            createUseCase(CaptureType.VIDEO_CAPTURE) // VIDEO
+        val useCase2 =
+            createUseCase(CaptureType.PREVIEW, imageFormat = ImageFormat.JPEG) // PREVIEW
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, RECORD_SIZE)
+            put(useCase2, RECORD_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun populateStreamUseCaseStreamSpecOptionWithSupportedSurfaceConfigs_wrongCaptureType() {
+        val useCase1 =
+            createUseCase(CaptureType.PREVIEW) // PREVIEW
+        val useCase2 =
+            createUseCase(CaptureType.PREVIEW) // PREVIEW
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, RECORD_SIZE)
+            put(useCase2, PREVIEW_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+    }
+
     /**
      * Sets up camera according to the specified settings and initialize [CameraX].
      *
@@ -3205,58 +3317,76 @@
 
             // setup to return different minimum frame durations depending on resolution
             // minimum frame durations were designated only for the purpose of testing
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(4032, 3024))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(4032, 3024))
+                )
+            )
                 .thenReturn(50000000L) // 20 fps, size maximum
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(3840, 2160))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(3840, 2160))
+                )
+            )
                 .thenReturn(40000000L) // 25, size record
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(1920, 1440))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(1920, 1440))
+                )
+            )
                 .thenReturn(33333333L) // 30
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(1920, 1080))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(1920, 1080))
+                )
+            )
                 .thenReturn(28571428L) // 35
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(1280, 960))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(1280, 960))
+                )
+            )
                 .thenReturn(25000000L) // 40
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(1280, 720))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(1280, 720))
+                )
+            )
                 .thenReturn(22222222L) // 45, size preview/display
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(960, 544))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(960, 544))
+                )
+            )
                 .thenReturn(20000000L) // 50
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(800, 450))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(800, 450))
+                )
+            )
                 .thenReturn(16666666L) // 60fps
 
-            Mockito.`when`(map.getOutputMinFrameDuration(
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.eq(Size(640, 480))
-            ))
+            Mockito.`when`(
+                map.getOutputMinFrameDuration(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.eq(Size(640, 480))
+                )
+            )
                 .thenReturn(16666666L) // 60fps
 
             // Sets up the supported high resolution sizes
@@ -3268,7 +3398,8 @@
 
         val maximumResolutionMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
             (maximumResolutionSupportedSizes != null ||
-                maximumResolutionHighResolutionSupportedSizes != null)) {
+                maximumResolutionHighResolutionSupportedSizes != null)
+        ) {
             Mockito.mock(StreamConfigurationMap::class.java).also {
                 Mockito.`when`(it.getOutputSizes(ArgumentMatchers.anyInt()))
                     .thenReturn(maximumResolutionSupportedSizes)
@@ -3289,7 +3420,8 @@
             Range(30, 40),
             Range(30, 60),
             Range(50, 60),
-            Range(60, 60))
+            Range(60, 60)
+        )
 
         val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
         Shadow.extract<ShadowCameraCharacteristics>(characteristics).apply {
@@ -3413,15 +3545,18 @@
         captureType: CaptureType,
         targetFrameRate: Range<Int>? = null,
         dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED,
-        streamUseCaseOverride: Boolean
+        streamUseCaseOverride: Boolean = false,
+        imageFormat: Int? = null
     ): UseCase {
         val builder = FakeUseCaseConfig.Builder(
-            captureType, when (captureType) {
-                CaptureType.PREVIEW -> ImageFormat.PRIVATE
-                CaptureType.IMAGE_CAPTURE -> ImageFormat.JPEG
-                CaptureType.IMAGE_ANALYSIS -> ImageFormat.YUV_420_888
-                else -> INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
-            }
+            captureType, imageFormat
+                ?: when (captureType) {
+                    CaptureType.PREVIEW -> ImageFormat.PRIVATE
+                    CaptureType.IMAGE_CAPTURE -> ImageFormat.JPEG
+                    CaptureType.IMAGE_ANALYSIS -> ImageFormat.YUV_420_888
+                    CaptureType.VIDEO_CAPTURE -> ImageFormat.PRIVATE
+                    else -> INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+                }
         )
         targetFrameRate?.let {
             builder.mutableConfig.insertOption(UseCaseConfig.OPTION_TARGET_FRAME_RATE, it)
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
index 74fd98c..26cc0fd 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
@@ -143,8 +143,8 @@
             // Checks the combination is supported by the list retrieved from the
             // ExtraSupportedSurfaceCombinationsContainer.
             for (SurfaceCombination extraSurfaceCombination : extraSurfaceCombinations) {
-                if (extraSurfaceCombination.isSupported(
-                        expectedSupportedSurfaceCombination.getSurfaceConfigList())) {
+                if (extraSurfaceCombination.getOrderedSupportedSurfaceConfigList(
+                        expectedSupportedSurfaceCombination.getSurfaceConfigList()) != null) {
                     isSupported = true;
                     break;
                 }
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index bf6443f..a5ebf79 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -169,6 +169,30 @@
     ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
   }
 
+  @RequiresApi(21) public final class DynamicRange {
+    ctor public DynamicRange(int, int);
+    method public int getBitDepth();
+    method public int getEncoding();
+    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+    field public static final int ENCODING_HDR10 = 4; // 0x4
+    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+    field public static final int ENCODING_HLG = 3; // 0x3
+    field public static final int ENCODING_SDR = 1; // 0x1
+    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+    field public static final androidx.camera.core.DynamicRange SDR;
+    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+  }
+
   @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
@@ -463,6 +487,7 @@
   @RequiresApi(21) public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public void clearTransformationInfoListener();
+    method public androidx.camera.core.DynamicRange getDynamicRange();
     method public android.util.Size getResolution();
     method public boolean invalidate();
     method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index bf6443f..a5ebf79 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -169,6 +169,30 @@
     ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
   }
 
+  @RequiresApi(21) public final class DynamicRange {
+    ctor public DynamicRange(int, int);
+    method public int getBitDepth();
+    method public int getEncoding();
+    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+    field public static final int ENCODING_HDR10 = 4; // 0x4
+    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+    field public static final int ENCODING_HLG = 3; // 0x3
+    field public static final int ENCODING_SDR = 1; // 0x1
+    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+    field public static final androidx.camera.core.DynamicRange SDR;
+    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+  }
+
   @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
@@ -463,6 +487,7 @@
   @RequiresApi(21) public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public void clearTransformationInfoListener();
+    method public androidx.camera.core.DynamicRange getDynamicRange();
     method public android.util.Size getResolution();
     method public boolean invalidate();
     method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java b/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java
index 535d0f9..6ecd61e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java
@@ -47,7 +47,6 @@
  * @see androidx.camera.video.VideoCapture.Builder#setDynamicRange(DynamicRange)
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public final class DynamicRange {
     /**
      * An unspecified dynamic range encoding which allows the device to determine the underlying
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
index b68ceca..70cc414 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
@@ -338,7 +338,6 @@
      * {@link android.graphics.ImageFormat#PRIVATE} or
      * {@link android.graphics.ImageFormat#YCBCR_P010}.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @NonNull
     public DynamicRange getDynamicRange() {
         return mDynamicRange;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/AttachedSurfaceInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/AttachedSurfaceInfo.java
index 8fe27de..3f4b1cd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/AttachedSurfaceInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/AttachedSurfaceInfo.java
@@ -55,6 +55,23 @@
                 dynamicRange, captureTypes, implementationOptions, targetFrameRate);
     }
 
+    /**
+     * Obtains the StreamSpec from the given AttachedSurfaceInfo with the given
+     * implementationOptions.
+     */
+    @NonNull
+    public StreamSpec toStreamSpec(
+            @NonNull Config implementationOptions) {
+        StreamSpec.Builder streamSpecBuilder =
+                StreamSpec.builder(getSize())
+                        .setDynamicRange(getDynamicRange())
+                        .setImplementationOptions(implementationOptions);
+        if (getTargetFrameRate() != null) {
+            streamSpecBuilder.setExpectedFrameRateRange(getTargetFrameRate());
+        }
+        return streamSpecBuilder.build();
+    }
+
     /** Returns the SurfaceConfig. */
     @NonNull
     public abstract SurfaceConfig getSurfaceConfig();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceCombination.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceCombination.java
index 43ada23..8109249 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceCombination.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceCombination.java
@@ -17,9 +17,11 @@
 package androidx.camera.core.impl;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -79,16 +81,21 @@
 
     /**
      * Check whether the input surface configuration list is under the capability of the combination
-     * of this object.
+     * of this object. If so, return the supporting combination ordered such that the
+     * SurfaceConfig at each position of the returned list is the one that supports the
+     * SurfaceConfig at the same position of the input list.
      *
      * @param configList the surface configuration list to be compared
-     * @return the check result that whether it could be supported
+     * @return the ordered surface configuration list or {@code null} if the configuration list
+     * is not supported by this combination.
      */
-    public boolean isSupported(@NonNull List<SurfaceConfig> configList) {
+    @Nullable
+    public List<SurfaceConfig> getOrderedSupportedSurfaceConfigList(
+            @NonNull List<SurfaceConfig> configList) {
         boolean isSupported = false;
 
         if (configList.isEmpty()) {
-            return true;
+            return new ArrayList<>();
         }
 
         /**
@@ -98,10 +105,11 @@
          * MAXIMUM) + (RAW, MAXIMUM).
          */
         if (configList.size() > mSurfaceConfigList.size()) {
-            return false;
+            return null;
         }
 
         List<int[]> elementsArrangements = getElementsArrangements(mSurfaceConfigList.size());
+        SurfaceConfig[] surfaceConfigArray = new SurfaceConfig[configList.size()];
 
         for (int[] elementsArrangement : elementsArrangements) {
             boolean checkResult = true;
@@ -115,6 +123,9 @@
 
                     if (!checkResult) {
                         break;
+                    } else {
+                        surfaceConfigArray[elementsArrangement[index]] =
+                                mSurfaceConfigList.get(index);
                     }
                 }
             }
@@ -125,7 +136,7 @@
             }
         }
 
-        return isSupported;
+        return isSupported ? Arrays.asList(surfaceConfigArray) : null;
     }
 
     private List<int[]> getElementsArrangements(int n) {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
index 9875af2..2ec214e 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -375,6 +375,7 @@
          *                             as part of this callback. Both Camera2 and CameraX guarantee
          *                             that those two settings and results are always supported and
          *                             applied by the corresponding framework.
+         * @since 1.3
          */
         void onCaptureCompleted(long timestamp, int captureSequenceId,
                 @NonNull Map<CaptureResult.Key, Object> result);
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index 5b4653b..8b7f803 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -94,8 +94,8 @@
     method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
     method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
     method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-    method public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
-    method public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
   }
 
   @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
@@ -103,6 +103,7 @@
     method public java.util.concurrent.Executor? getExecutor();
     method public androidx.camera.video.QualitySelector getQualitySelector();
     method public int getTargetVideoEncodingBitRate();
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
     method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
     method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
     method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
@@ -133,7 +134,14 @@
     method public abstract long getRecordedDurationNanos();
   }
 
+  @RequiresApi(21) public interface VideoCapabilities {
+    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+  }
+
   @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
     method public int getMirrorMode();
     method public T getOutput();
     method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
@@ -146,6 +154,7 @@
   @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
     ctor public VideoCapture.Builder(T);
     method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
     method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
     method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
     method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index 5b4653b..8b7f803 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -94,8 +94,8 @@
     method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
     method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
     method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-    method public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
-    method public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
   }
 
   @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
@@ -103,6 +103,7 @@
     method public java.util.concurrent.Executor? getExecutor();
     method public androidx.camera.video.QualitySelector getQualitySelector();
     method public int getTargetVideoEncodingBitRate();
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
     method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
     method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
     method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
@@ -133,7 +134,14 @@
     method public abstract long getRecordedDurationNanos();
   }
 
+  @RequiresApi(21) public interface VideoCapabilities {
+    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+  }
+
   @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
     method public int getMirrorMode();
     method public T getOutput();
     method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
@@ -146,6 +154,7 @@
   @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
     ctor public VideoCapture.Builder(T);
     method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
     method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
     method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
     method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolverTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolverTest.kt
index 639bc32..ea11ae8 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolverTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolverTest.kt
@@ -25,6 +25,7 @@
 import androidx.camera.core.impl.Timebase
 import androidx.camera.testing.EncoderProfilesUtil
 import androidx.camera.video.VideoSpec
+import androidx.camera.video.internal.encoder.VideoEncoderDataSpace
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -287,6 +288,88 @@
         }
     }
 
+    @Test
+    fun dataSpaceIsUnspecified_forUnsupportedMime() {
+        testMimeAndDynamicRangeResolvesToDataSpace(
+            UNSUPPORTED_MIME_TYPE,
+            DynamicRange.HLG_10_BIT,
+            VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED
+        )
+    }
+
+    @Test
+    fun dataSpaceIsChosenFromDynamicRange_hevc() {
+        val dynamicRangeToExpectedDataSpaces = mapOf(
+            // For backward compatibility, SDR maps to UNSPECIFIED
+            DynamicRange.SDR to VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED,
+            DynamicRange.HLG_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_HLG,
+            DynamicRange.HDR10_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+            DynamicRange.HDR10_PLUS_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+        )
+
+        for (entry in dynamicRangeToExpectedDataSpaces) {
+            testMimeAndDynamicRangeResolvesToDataSpace(
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                entry.key,
+                entry.value
+            )
+        }
+    }
+
+    @Test
+    fun dataSpaceIsChosenFromDynamicRange_av1() {
+        val dynamicRangeToExpectedDataSpaces = mapOf(
+            // For backward compatibility, SDR maps to UNSPECIFIED
+            DynamicRange.SDR to VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED,
+            DynamicRange.HLG_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_HLG,
+            DynamicRange.HDR10_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+            DynamicRange.HDR10_PLUS_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+        )
+
+        for (entry in dynamicRangeToExpectedDataSpaces) {
+            testMimeAndDynamicRangeResolvesToDataSpace(
+                MediaFormat.MIMETYPE_VIDEO_AV1,
+                entry.key,
+                entry.value
+            )
+        }
+    }
+
+    @Test
+    fun dataSpaceIsChosenFromDynamicRange_vp9() {
+        val dynamicRangeToExpectedDataSpaces = mapOf(
+            // For backward compatibility, SDR maps to UNSPECIFIED
+            DynamicRange.SDR to VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED,
+            DynamicRange.HLG_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_HLG,
+            DynamicRange.HDR10_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+            DynamicRange.HDR10_PLUS_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ,
+        )
+
+        for (entry in dynamicRangeToExpectedDataSpaces) {
+            testMimeAndDynamicRangeResolvesToDataSpace(
+                MediaFormat.MIMETYPE_VIDEO_VP9,
+                entry.key,
+                entry.value
+            )
+        }
+    }
+
+    @Test
+    fun dataSpaceIsChosenFromDynamicRange_dolbyVision() {
+        val dynamicRangeToExpectedDataSpaces = mapOf(
+            DynamicRange.DOLBY_VISION_10_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_HLG,
+            DynamicRange.DOLBY_VISION_8_BIT to VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT709,
+        )
+
+        for (entry in dynamicRangeToExpectedDataSpaces) {
+            testMimeAndDynamicRangeResolvesToDataSpace(
+                MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
+                entry.key,
+                entry.value
+            )
+        }
+    }
+
     private fun testMimeAndDynamicRangeResolveToProfile(
         mime: String,
         dynamicRange: DynamicRange,
@@ -306,4 +389,23 @@
             expectedProfile
         )
     }
+
+    private fun testMimeAndDynamicRangeResolvesToDataSpace(
+        mime: String,
+        dynamicRange: DynamicRange,
+        expectedDataSpace: VideoEncoderDataSpace,
+    ) {
+        assertThat(
+            VideoEncoderConfigDefaultResolver(
+                mime,
+                TIMEBASE,
+                DEFAULT_VIDEO_SPEC,
+                EncoderProfilesUtil.RESOLUTION_1080P,
+                dynamicRange,
+                SurfaceRequest.FRAME_RATE_RANGE_UNSPECIFIED
+            ).get().dataSpace
+        ).isEqualTo(
+            expectedDataSpace
+        )
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolverTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolverTest.kt
index e32b19f..9a321d4 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolverTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolverTest.kt
@@ -34,6 +34,7 @@
 import androidx.camera.video.Recorder
 import androidx.camera.video.VideoCapabilities
 import androidx.camera.video.VideoSpec
+import androidx.camera.video.internal.encoder.VideoEncoderDataSpace
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -348,4 +349,38 @@
             }
         }
     }
+
+    @Test
+    fun supportedHdrDynamicRanges_mapToSpecifiedVideoEncoderDataSpace() {
+        dynamicRanges.forEach { dynamicRange ->
+            val supportedProfiles = videoCapabilities.getSupportedQualities(dynamicRange).flatMap {
+                videoCapabilities.getProfiles(it, dynamicRange)!!.videoProfiles
+            }.toSet()
+
+            supportedProfiles.forEach { videoProfile ->
+                val surfaceSize = Size(videoProfile.width, videoProfile.height)
+
+                val resolvedDataSpace = VideoEncoderConfigVideoProfileResolver(
+                    videoProfile.mediaType,
+                    timebase,
+                    defaultVideoSpec,
+                    surfaceSize,
+                    videoProfile,
+                    dynamicRange,
+                    Range(videoProfile.frameRate, videoProfile.frameRate)
+                ).get().dataSpace
+
+                // SDR should always map to UNSPECIFIED, while others should not
+                if (dynamicRange == DynamicRange.SDR) {
+                    assertThat(resolvedDataSpace).isEqualTo(
+                        VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED
+                    )
+                } else {
+                    assertThat(resolvedDataSpace).isNotEqualTo(
+                        VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED
+                    )
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java b/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
index 14b257a..19cefa9 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
@@ -96,7 +96,10 @@
      * in the returned list, but their corresponding qualities are included.
      *
      * @param cameraInfo the cameraInfo
+     *
+     * @deprecated use {@link VideoCapabilities#getSupportedQualities(DynamicRange)} instead.
      */
+    @Deprecated
     @NonNull
     public static List<Quality> getSupportedQualities(@NonNull CameraInfo cameraInfo) {
         return Recorder.getVideoCapabilities(cameraInfo).getSupportedQualities(SDR);
@@ -119,7 +122,10 @@
      * @param quality one of the quality constants.
      * @return {@code true} if the quality is supported; {@code false} otherwise.
      * @see #getSupportedQualities(CameraInfo)
+     *
+     * @deprecated use {@link VideoCapabilities#isQualitySupported(Quality, DynamicRange)} instead.
      */
+    @Deprecated
     public static boolean isQualitySupported(@NonNull CameraInfo cameraInfo,
             @NonNull Quality quality) {
         return Recorder.getVideoCapabilities(cameraInfo).isQualitySupported(quality, SDR);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index d14e237..542578e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -2727,9 +2727,15 @@
     }
 
     /**
-     * Gets the {@link VideoCapabilities} of Recorder.
+     * Returns the {@link VideoCapabilities} of Recorder with respect to input camera information.
+     *
+     * <p>{@link VideoCapabilities} provides methods to query supported dynamic ranges and
+     * qualities. This information can be used for things like checking if HDR is supported for
+     * configuring VideoCapture to record HDR video.
+     *
+     * @param cameraInfo info about the camera.
+     * @return VideoCapabilities with respect to the input camera info.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @NonNull
     public static VideoCapabilities getVideoCapabilities(@NonNull CameraInfo cameraInfo) {
         return RecorderVideoCapabilities.from(cameraInfo);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
index 0d8521c..02b857e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
@@ -32,8 +32,19 @@
 
 /**
  * VideoCapabilities is used to query video recording capabilities on the device.
+ *
+ * <p>Take {@link Recorder} as an example, the supported {@link DynamicRange}s can be queried with
+ * the following code:
+ * <pre>{@code
+ *   VideoCapabilities videoCapabilities = Recorder.getVideoCapabilities(cameraInfo);
+ *   Set<DynamicRange> supportedDynamicRanges = videoCapabilities.getSupportedDynamicRanges();
+ * }</pre>
+ * <p>The query result can be used to check if high dynamic range (HDR) recording is
+ * supported, and to get the supported qualities of the target {@link DynamicRange}:
+ * <pre>{@code
+ *   List<Quality> supportedQualities = videoCapabilities.getSupportedQualities(dynamicRange);
+ * }</pre>
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface VideoCapabilities {
 
@@ -45,6 +56,8 @@
      * {@link DynamicRange}s such as {@link DynamicRange#HDR_UNSPECIFIED_10_BIT} will not be
      * included, but they can be used in other methods, such as checking for quality support with
      * {@link #isQualitySupported(Quality, DynamicRange)}.
+     *
+     * @return a set of supported dynamic ranges.
      */
     @NonNull
     Set<DynamicRange> getSupportedDynamicRanges();
@@ -52,9 +65,25 @@
     /**
      * Gets all supported qualities for the input dynamic range.
      *
-     * <p>The returned list is sorted by quality size from large to small.
+     * <p>The returned list is sorted by quality size from largest to smallest. For the qualities in
+     * the returned list, with the same input dynamicRange,
+     * {@link #isQualitySupported(Quality, DynamicRange)} will return {@code true}.
      *
-     * <p>Note: Constants {@link Quality#HIGHEST} and {@link Quality#LOWEST} are not included.
+     * <p>When the {@code dynamicRange} is not fully specified, e.g.
+     * {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}, the returned list is the union of the
+     * qualities supported by the matching fully specified dynamic ranges. This does not mean
+     * that all returned qualities are available for every matching dynamic range. Therefore, it
+     * is not recommended to rely on any one particular quality to work if mixing use cases with
+     * other dynamic ranges.
+     *
+     * <p>Note: Constants {@link Quality#HIGHEST} and {@link Quality#LOWEST} are not included in
+     * the returned list, but their corresponding qualities are included. For example: when the
+     * returned list consists of {@link Quality#UHD}, {@link Quality#FHD} and {@link Quality#HD},
+     * {@link Quality#HIGHEST} corresponds to {@link Quality#UHD}, which is the highest quality,
+     * and {@link Quality#LOWEST} corresponds to {@link Quality#HD}.
+     *
+     * @param dynamicRange the dynamicRange.
+     * @return a list of supported qualities sorted by size from large to small.
      */
     @NonNull
     List<Quality> getSupportedQualities(@NonNull DynamicRange dynamicRange);
@@ -62,11 +91,25 @@
     /**
      * Checks if the quality is supported for the input dynamic range.
      *
-     * @param quality one of the quality constants. Possible values include
-     *                {@link Quality#LOWEST}, {@link Quality#HIGHEST}, {@link Quality#SD},
-     *                {@link Quality#HD}, {@link Quality#FHD}, or {@link Quality#UHD}.
-     * @param dynamicRange the target dynamicRange.
+     * <p>Calling this method with one of the qualities contained in the returned list of
+     * {@link #getSupportedQualities(DynamicRange)} will return {@code true}.
+     *
+     * <p>Possible values for {@code quality} include {@link Quality#LOWEST},
+     * {@link Quality#HIGHEST}, {@link Quality#SD}, {@link Quality#HD}, {@link Quality#FHD}
+     * and {@link Quality#UHD}.
+     *
+     * <p>If this method is called with {@link Quality#LOWEST} or {@link Quality#HIGHEST}, it
+     * will return {@code true} except the case that none of the qualities can be supported.
+     *
+     * <p>When the {@code dynamicRange} is not fully specified, e.g.
+     * {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}, {@code true} will be returned if there is any
+     * matching fully specified dynamic range supporting the {@code quality}, otherwise {@code
+     * false} will be returned.
+     *
+     * @param quality one of the quality constants.
+     * @param dynamicRange the dynamicRange.
      * @return {@code true} if the quality is supported; {@code false} otherwise.
+     * @see #getSupportedQualities(DynamicRange)
      */
     boolean isQualitySupported(@NonNull Quality quality, @NonNull DynamicRange dynamicRange);
 
@@ -130,6 +173,8 @@
     }
 
     /** An empty implementation. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @NonNull
     VideoCapabilities EMPTY = new VideoCapabilities() {
         @NonNull
         @Override
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 3858066..60023cc 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -443,7 +443,6 @@
     // that will be sent to the VideoOutput. That should always be retrieved from the StreamSpec
     // since that will be the final DynamicRange chosen by the camera based on other use case
     // combinations.
-    @RestrictTo(Scope.LIBRARY)
     @NonNull
     public DynamicRange getDynamicRange() {
         return getCurrentConfig().hasDynamicRange() ? getCurrentConfig().getDynamicRange() :
@@ -1682,7 +1681,6 @@
          * @return The current Builder.
          * @see DynamicRange
          */
-        @RestrictTo(Scope.LIBRARY)
         @NonNull
         @Override
         public Builder<T> setDynamicRange(@NonNull DynamicRange dynamicRange) {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoConfigUtil.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoConfigUtil.java
index 6d7d97f6..ec866e1 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoConfigUtil.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoConfigUtil.java
@@ -16,6 +16,30 @@
 
 package androidx.camera.video.internal.config;
 
+import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10Plus;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8;
+import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe;
+import static android.media.MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile0;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile1;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile3;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile3HDR;
+import static android.media.MediaCodecInfo.CodecProfileLevel.VP9Profile3HDR10Plus;
+
+import static androidx.camera.video.internal.encoder.VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_HLG;
+import static androidx.camera.video.internal.encoder.VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT2020_PQ;
+import static androidx.camera.video.internal.encoder.VideoEncoderDataSpace.ENCODER_DATA_SPACE_BT709;
+import static androidx.camera.video.internal.encoder.VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED;
+
 import android.media.MediaFormat;
 import android.util.Range;
 import android.util.Rational;
@@ -32,10 +56,13 @@
 import androidx.camera.video.VideoSpec;
 import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
+import androidx.camera.video.internal.encoder.VideoEncoderDataSpace;
 import androidx.camera.video.internal.utils.DynamicRangeUtil;
 import androidx.core.util.Preconditions;
 import androidx.core.util.Supplier;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -46,10 +73,58 @@
 public final class VideoConfigUtil {
     private static final String TAG = "VideoConfigUtil";
 
+    private static final Map<String, Map<Integer, VideoEncoderDataSpace>> MIME_TO_DATA_SPACE_MAP =
+            new HashMap<>();
+
     // Should not be instantiated.
     private VideoConfigUtil() {
     }
 
+    static {
+        //--------------------------------------------------------------------------------------//
+        // Mime and profile level to encoder data space map                                     //
+        //--------------------------------------------------------------------------------------//
+        Map<Integer, VideoEncoderDataSpace> profHevcMap = new HashMap<>();
+        // We treat SDR (main profile) as unspecified. Allow the encoder to use default data space.
+        profHevcMap.put(HEVCProfileMain, ENCODER_DATA_SPACE_UNSPECIFIED);
+        profHevcMap.put(HEVCProfileMain10, ENCODER_DATA_SPACE_BT2020_HLG);
+        profHevcMap.put(HEVCProfileMain10HDR10, ENCODER_DATA_SPACE_BT2020_PQ);
+        profHevcMap.put(HEVCProfileMain10HDR10Plus, ENCODER_DATA_SPACE_BT2020_PQ);
+
+        Map<Integer, VideoEncoderDataSpace> profAv1Map = new HashMap<>();
+        // We treat SDR (main 8 profile) as unspecified. Allow the encoder to use default data
+        // space.
+        profAv1Map.put(AV1ProfileMain8, ENCODER_DATA_SPACE_UNSPECIFIED);
+        profAv1Map.put(AV1ProfileMain10, ENCODER_DATA_SPACE_BT2020_HLG);
+        profAv1Map.put(AV1ProfileMain10HDR10, ENCODER_DATA_SPACE_BT2020_PQ);
+        profAv1Map.put(AV1ProfileMain10HDR10Plus, ENCODER_DATA_SPACE_BT2020_PQ);
+
+        Map<Integer, VideoEncoderDataSpace> profVp9Map = new HashMap<>();
+        // We treat SDR (profile 0) as unspecified. Allow the encoder to use default data space.
+        profVp9Map.put(VP9Profile0, ENCODER_DATA_SPACE_UNSPECIFIED);
+        profVp9Map.put(VP9Profile2, ENCODER_DATA_SPACE_BT2020_HLG);
+        profVp9Map.put(VP9Profile2HDR, ENCODER_DATA_SPACE_BT2020_PQ);
+        profVp9Map.put(VP9Profile2HDR10Plus, ENCODER_DATA_SPACE_BT2020_PQ);
+        // Vp9 4:2:2 profiles
+        profVp9Map.put(VP9Profile1, ENCODER_DATA_SPACE_UNSPECIFIED);
+        profVp9Map.put(VP9Profile3, ENCODER_DATA_SPACE_BT2020_HLG);
+        profVp9Map.put(VP9Profile3HDR, ENCODER_DATA_SPACE_BT2020_PQ);
+        profVp9Map.put(VP9Profile3HDR10Plus, ENCODER_DATA_SPACE_BT2020_PQ);
+
+        Map<Integer, VideoEncoderDataSpace> profDvMap = new HashMap<>();
+        // For Dolby Vision profile 8, we only support 8.4 (10-bit HEVC HLG)
+        profDvMap.put(DolbyVisionProfileDvheSt, ENCODER_DATA_SPACE_BT2020_HLG);
+        // For Dolby Vision profile 9, we only support 9.2 (8-bit AVC SDR BT.709)
+        profDvMap.put(DolbyVisionProfileDvavSe, ENCODER_DATA_SPACE_BT709);
+
+        // Combine all mime type maps
+        MIME_TO_DATA_SPACE_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, profHevcMap);
+        MIME_TO_DATA_SPACE_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, profAv1Map);
+        MIME_TO_DATA_SPACE_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, profVp9Map);
+        MIME_TO_DATA_SPACE_MAP.put(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION, profDvMap);
+        //--------------------------------------------------------------------------------------//
+    }
+
     /**
      * Resolves the video mime information into a {@link VideoMimeInfo}.
      *
@@ -230,4 +305,27 @@
         Logger.d(TAG, debugString);
         return resolvedBitrate;
     }
+
+    /**
+     * Returns the encoder data space for the given mime and profile.
+     *
+     * @return The data space for the given mime type and profile, or
+     * {@link VideoEncoderDataSpace#ENCODER_DATA_SPACE_UNSPECIFIED} if the profile represents SDR or is unsupported.
+     */
+    @NonNull
+    public static VideoEncoderDataSpace mimeAndProfileToEncoderDataSpace(@NonNull String mimeType,
+            int codecProfileLevel) {
+        Map<Integer, VideoEncoderDataSpace> profileToDataSpaceMap =
+                MIME_TO_DATA_SPACE_MAP.get(mimeType);
+        if (profileToDataSpaceMap != null) {
+            VideoEncoderDataSpace dataSpace = profileToDataSpaceMap.get(codecProfileLevel);
+            if (dataSpace != null) {
+                return dataSpace;
+            }
+        }
+
+        Logger.w(TAG, String.format("Unsupported mime type %s or profile level %d. Data space is "
+                + "unspecified.", mimeType, codecProfileLevel));
+        return ENCODER_DATA_SPACE_UNSPECIFIED;
+    }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolver.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolver.java
index 8aed0f6..a77d6f2 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolver.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigDefaultResolver.java
@@ -27,6 +27,7 @@
 import androidx.camera.core.impl.Timebase;
 import androidx.camera.video.VideoSpec;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
+import androidx.camera.video.internal.encoder.VideoEncoderDataSpace;
 import androidx.camera.video.internal.utils.DynamicRangeUtil;
 import androidx.core.util.Supplier;
 
@@ -107,6 +108,8 @@
 
         int resolvedProfile = DynamicRangeUtil.dynamicRangeToCodecProfileLevelForMime(
                 mMimeType, mDynamicRange);
+        VideoEncoderDataSpace dataSpace =
+                VideoConfigUtil.mimeAndProfileToEncoderDataSpace(mMimeType, resolvedProfile);
 
         return VideoEncoderConfig.builder()
                 .setMimeType(mMimeType)
@@ -115,6 +118,7 @@
                 .setBitrate(resolvedBitrate)
                 .setFrameRate(resolvedFrameRate)
                 .setProfile(resolvedProfile)
+                .setDataSpace(dataSpace)
                 .build();
     }
 
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolver.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolver.java
index 4b1a958..974ef81 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolver.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/config/VideoEncoderConfigVideoProfileResolver.java
@@ -28,6 +28,7 @@
 import androidx.camera.core.impl.Timebase;
 import androidx.camera.video.VideoSpec;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
+import androidx.camera.video.internal.encoder.VideoEncoderDataSpace;
 import androidx.core.util.Supplier;
 
 import java.util.Objects;
@@ -101,6 +102,8 @@
                 videoSpecBitrateRange);
 
         int resolvedProfile = mVideoProfile.getProfile();
+        VideoEncoderDataSpace dataSpace =
+                VideoConfigUtil.mimeAndProfileToEncoderDataSpace(mMimeType, resolvedProfile);
 
         return VideoEncoderConfig.builder()
                 .setMimeType(mMimeType)
@@ -109,6 +112,7 @@
                 .setBitrate(resolvedBitrate)
                 .setFrameRate(resolvedFrameRate)
                 .setProfile(resolvedProfile)
+                .setDataSpace(dataSpace)
                 .build();
     }
 
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderConfig.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderConfig.java
index dfe5a49..42e3618 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderConfig.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderConfig.java
@@ -45,7 +45,8 @@
         return new AutoValue_VideoEncoderConfig.Builder()
                 .setProfile(EncoderConfig.CODEC_PROFILE_NONE)
                 .setIFrameInterval(VIDEO_INTRA_FRAME_INTERVAL_DEFAULT)
-                .setColorFormat(VIDEO_COLOR_FORMAT_DEFAULT);
+                .setColorFormat(VIDEO_COLOR_FORMAT_DEFAULT)
+                .setDataSpace(VideoEncoderDataSpace.ENCODER_DATA_SPACE_UNSPECIFIED);
     }
 
     @Override
@@ -66,6 +67,10 @@
     /** Gets the color format. */
     public abstract int getColorFormat();
 
+    /** Gets the color data space. */
+    @NonNull
+    public abstract VideoEncoderDataSpace getDataSpace();
+
     /** Gets the frame rate. */
     public abstract int getFrameRate();
 
@@ -89,6 +94,16 @@
         if (getProfile() != EncoderConfig.CODEC_PROFILE_NONE) {
             format.setInteger(MediaFormat.KEY_PROFILE, getProfile());
         }
+        VideoEncoderDataSpace dataSpace = getDataSpace();
+        if (dataSpace.getStandard() != VideoEncoderDataSpace.VIDEO_COLOR_STANDARD_UNSPECIFIED) {
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, dataSpace.getStandard());
+        }
+        if (dataSpace.getTransfer() != VideoEncoderDataSpace.VIDEO_COLOR_TRANSFER_UNSPECIFIED) {
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, dataSpace.getTransfer());
+        }
+        if (dataSpace.getRange() != VideoEncoderDataSpace.VIDEO_COLOR_RANGE_UNSPECIFIED) {
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, dataSpace.getRange());
+        }
         return format;
     }
 
@@ -119,6 +134,10 @@
         @NonNull
         public abstract Builder setColorFormat(int colorFormat);
 
+        /** Sets the color data space. */
+        @NonNull
+        public abstract Builder setDataSpace(@NonNull VideoEncoderDataSpace dataSpace);
+
         /** Sets the frame rate. */
         @NonNull
         public abstract Builder setFrameRate(int frameRate);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpace.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpace.java
new file mode 100644
index 0000000..4fd914b
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpace.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.encoder;
+
+import android.media.MediaFormat;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Defines the three components of colors used by an encoder.
+ *
+ * <p>This is the encoder equivalent of {@link android.hardware.DataSpace}, and should be used to
+ * communicate the {@link MediaFormat} keys needed by the encoder.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@AutoValue
+public abstract class VideoEncoderDataSpace {
+
+    /** Standard characteristics that are unknown or specified by the device defaults. */
+    public static final int VIDEO_COLOR_STANDARD_UNSPECIFIED = 0;
+
+    /** Color transfer function that is unknown or specified by the device defaults. */
+    public static final int VIDEO_COLOR_TRANSFER_UNSPECIFIED = 0;
+
+    /** Range characteristics that are unknown or specified by the device defaults. */
+    public static final int VIDEO_COLOR_RANGE_UNSPECIFIED = 0;
+
+    /** A data space where all components are unspecified. */
+    public static final VideoEncoderDataSpace ENCODER_DATA_SPACE_UNSPECIFIED =
+            create(VIDEO_COLOR_STANDARD_UNSPECIFIED,
+                    VIDEO_COLOR_TRANSFER_UNSPECIFIED,
+                    VIDEO_COLOR_RANGE_UNSPECIFIED);
+
+    /**
+     * Color standard BT.709 with SDR video transfer function.
+     *
+     * <p>This mirrors the data space from {@link android.hardware.DataSpace#DATASPACE_BT709}.
+     */
+    public static final VideoEncoderDataSpace ENCODER_DATA_SPACE_BT709 =
+            create(MediaFormat.COLOR_STANDARD_BT709,
+                    MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
+                    MediaFormat.COLOR_RANGE_LIMITED);
+
+    /**
+     * Color standard BT.2020 with HLG transfer function.
+     *
+     * <p>This mirrors the data space from {@link android.hardware.DataSpace#DATASPACE_BT2020_HLG}.
+     */
+    public static final VideoEncoderDataSpace ENCODER_DATA_SPACE_BT2020_HLG =
+            create(MediaFormat.COLOR_STANDARD_BT2020,
+                    MediaFormat.COLOR_TRANSFER_HLG,
+                    MediaFormat.COLOR_RANGE_FULL);
+
+    /**
+     * Color standard BT.2020 with PQ (ST2084) transfer function.
+     *
+     * <p>This mirrors the data space from {@link android.hardware.DataSpace#DATASPACE_BT2020_PQ}.
+     */
+    public static final VideoEncoderDataSpace ENCODER_DATA_SPACE_BT2020_PQ =
+            create(MediaFormat.COLOR_STANDARD_BT2020,
+                    MediaFormat.COLOR_TRANSFER_ST2084,
+                    MediaFormat.COLOR_RANGE_FULL);
+
+    // Restrict constructor to same package
+    VideoEncoderDataSpace() {
+    }
+
+    /** Creates a data space from the three primaries. */
+    @NonNull
+    public static VideoEncoderDataSpace create(int standard, int transfer, int range) {
+        return new AutoValue_VideoEncoderDataSpace(standard, transfer, range);
+    }
+
+    /**
+     * Returns the color standard.
+     *
+     * <p>This will be one of {@link #VIDEO_COLOR_STANDARD_UNSPECIFIED} or one of the color
+     * standard constants defined in {@link MediaFormat}, such as
+     * {@link MediaFormat#COLOR_STANDARD_BT2020}.
+     */
+    public abstract int getStandard();
+
+    /**
+     * Returns the color transfer function.
+     *
+     * <p>This will be one of {@link #VIDEO_COLOR_TRANSFER_UNSPECIFIED} or one of the color
+     * transfer function constants defined in {@link MediaFormat}, such as
+     * {@link MediaFormat#COLOR_TRANSFER_HLG}.
+     */
+    public abstract int getTransfer();
+
+    /**
+     * Returns the color range.
+     *
+     * <p>This will be one of {@link #VIDEO_COLOR_RANGE_UNSPECIFIED} or one of the color
+     * range constants defined in {@link MediaFormat}, such as
+     * {@link MediaFormat#COLOR_RANGE_LIMITED}.
+     */
+    public abstract int getRange();
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
index d65651c..dd06e80 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
@@ -42,6 +42,7 @@
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
+@Suppress("DEPRECATION")
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class QualitySelectorTest {
 
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpaceTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpaceTest.kt
new file mode 100644
index 0000000..a7ef9c0
--- /dev/null
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/encoder/VideoEncoderDataSpaceTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.encoder
+
+import android.media.MediaFormat
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+private const val TEST_COLOR_STANDARD = MediaFormat.COLOR_STANDARD_BT2020
+private const val TEST_TRANSFER_FN = MediaFormat.COLOR_TRANSFER_HLG
+private const val TEST_COLOR_RANGE = MediaFormat.COLOR_RANGE_LIMITED
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class VideoEncoderDataSpaceTest {
+
+    @Test
+    fun canRetrieveFields() {
+        val dataSpace = VideoEncoderDataSpace.create(
+            TEST_COLOR_STANDARD,
+            TEST_TRANSFER_FN,
+            TEST_COLOR_RANGE
+        )
+
+        assertThat(dataSpace.standard).isEqualTo(TEST_COLOR_STANDARD)
+        assertThat(dataSpace.transfer).isEqualTo(TEST_TRANSFER_FN)
+        assertThat(dataSpace.range).isEqualTo(TEST_COLOR_RANGE)
+    }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index 17631cd..d43bd43 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -156,6 +156,7 @@
         assertThat(processor.isSurfaceRequestedAndProvided()).isTrue()
     }
 
+    @Ignore // b/283308005
     @Test
     fun enableEffect_imageCaptureEffectIsEnabled() {
         // Arrange: launch app and verify effect is inactive.
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 15fd553..1d68d6c 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -995,7 +995,7 @@
 
   @androidx.compose.foundation.ExperimentalFoundationApi public final class PagerDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.snapping.SnapFlingBehavior flingBehavior(androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.pager.PagerSnapDistance pagerSnapDistance, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional float snapVelocityThreshold, optional float snapPositionalThreshold);
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.gestures.Orientation orientation);
+    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
     field public static final androidx.compose.foundation.pager.PagerDefaults INSTANCE;
   }
 
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 15fd553..1d68d6c 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -995,7 +995,7 @@
 
   @androidx.compose.foundation.ExperimentalFoundationApi public final class PagerDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.snapping.SnapFlingBehavior flingBehavior(androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.pager.PagerSnapDistance pagerSnapDistance, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional float snapVelocityThreshold, optional float snapPositionalThreshold);
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.gestures.Orientation orientation);
+    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
     field public static final androidx.compose.foundation.pager.PagerDefaults INSTANCE;
   }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 4f41bb7..aa36946 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -108,6 +108,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.abs
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
@@ -1317,6 +1318,61 @@
     }
 
     @Test
+    fun scrollable_nestedFlingCancellation_shouldPreventDeltasFromPropagating() {
+        var childDeltas = 0f
+        var touchSlop = 0f
+        val childController = ScrollableState {
+            childDeltas += it
+            it
+        }
+        val flingCancellationParent = object : NestedScrollConnection {
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                if (source == NestedScrollSource.Fling && available != Offset.Zero) {
+                    throw CancellationException()
+                }
+                return Offset.Zero
+            }
+        }
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(modifier = Modifier.nestedScroll(flingCancellationParent)) {
+                Box(
+                    modifier = Modifier
+                        .size(600.dp)
+                        .testTag("childScrollable")
+                        .scrollable(childController, Orientation.Horizontal)
+                )
+            }
+        }
+
+        // First drag, this won't trigger the cancelation flow.
+        rule.onNodeWithTag("childScrollable").performTouchInput {
+            down(centerLeft)
+            moveBy(Offset(100f, 0f))
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(childDeltas).isEqualTo(100f - touchSlop)
+        }
+
+        childDeltas = 0f
+        var dragged = 0f
+        rule.onNodeWithTag("childScrollable").performTouchInput {
+            swipeWithVelocity(centerLeft, centerRight, 200f)
+            dragged = centerRight.x - centerLeft.x
+        }
+
+        // child didn't receive more deltas after drag, because fling was cancelled by the parent
+        assertThat(childDeltas).isEqualTo(dragged - touchSlop)
+    }
+
+    @Test
     fun scrollable_bothOrientations_proxiesPostFling() {
         val velocityFlung = 5000f
         val outerState = ScrollableState(consumeScrollDelta = { 0f })
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
index 41f1c73f..202b94e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
@@ -2060,6 +2060,35 @@
         }
     }
 
+    @Test
+    fun columnCountChange() {
+        var columnCount by mutableStateOf(2)
+        val containerCrossAxisSize = itemSizeDp * 2
+        rule.setContent {
+            LazyStaggeredGrid(
+                cells = columnCount,
+                maxSize = itemSizeDp,
+                crossAxisSize = containerCrossAxisSize
+            ) {
+                items(10, key = { it }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            columnCount = 1
+        }
+
+        onAnimationFrame { _ ->
+            // todo: proper animations when removal is supported
+            assertPositions(
+                0 to AxisOffset(0f, 0f),
+                1 to AxisOffset(itemSize, 0f)
+            )
+        }
+    }
+
     private fun AxisOffset(crossAxis: Float, mainAxis: Float) =
         if (isVertical) Offset(crossAxis, mainAxis) else Offset(mainAxis, crossAxis)
 
@@ -2160,6 +2189,7 @@
         startPadding: Dp = 0.dp,
         endPadding: Dp = 0.dp,
         spacing: Dp = 0.dp,
+        crossAxisSize: Dp? = null,
         content: LazyStaggeredGridScope.() -> Unit
     ) {
         state = rememberLazyStaggeredGridState(startIndex)
@@ -2168,7 +2198,7 @@
                 StaggeredGridCells.Fixed(cells),
                 Modifier
                     .requiredHeightIn(minSize, maxSize)
-                    .requiredWidth(itemSizeDp * cells)
+                    .requiredWidth(crossAxisSize ?: (itemSizeDp * cells))
                     .testTag(ContainerTag),
                 state = state,
                 verticalItemSpacing = spacing,
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
index 78b2758..4cfa189 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
@@ -16,7 +16,11 @@
 
 package androidx.compose.foundation.pager
 
+import androidx.compose.animation.splineBasedDecay
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.DefaultFlingBehavior
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -28,13 +32,16 @@
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
@@ -89,6 +96,58 @@
 
     @OptIn(ExperimentalFoundationApi::class)
     @Test
+    fun nestedScrollContent_shouldCancelFlingIfOnEdge() {
+        // Arrange
+        rule.mainClock.autoAdvance = false
+        val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(rule.density))
+        var flingTriggered = false
+        val flingInspector = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                flingTriggered = true
+                return with(defaultFlingBehavior) {
+                    performFling(initialVelocity)
+                }
+            }
+        }
+        createPager(pageCount = { DefaultPageCount }) {
+            LazyList(
+                modifier = Modifier.fillMaxSize(),
+                contentPadding = PaddingValues(0.dp),
+                isVertical = vertical, // scrollable content on the same direction as pager
+                reverseLayout = false,
+                flingBehavior = flingInspector,
+                state = rememberLazyListState(initialFirstVisibleItemIndex = 8),
+                userScrollEnabled = true,
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                verticalAlignment = Alignment.Top,
+                horizontalAlignment = Alignment.Start
+            ) {
+                items(10) {
+                    Box(modifier = Modifier.size(100.dp)) {
+                        BasicText(text = it.toString())
+                    }
+                }
+            }
+        }
+
+        // Act: High velocity swipe should fling inner list to edge
+        val forwardDelta = pagerSize * 0.5f * scrollForwardSign.toFloat()
+        rule.onNodeWithTag(TestTag).performTouchInput {
+            swipeWithVelocityAcrossMainAxis(10000f, forwardDelta)
+        }
+
+        rule.mainClock.advanceTimeUntil { flingTriggered } // wait for drag to finish
+
+        val previousOffset = pagerState.currentPageOffsetFraction
+        rule.mainClock.advanceTimeBy(1_000L) // advance time
+
+        // should've moved by then.
+        assertThat(pagerState.currentPageOffsetFraction).isNotEqualTo(previousOffset)
+    }
+
+    @OptIn(ExperimentalFoundationApi::class)
+    @Test
     fun nestedScrollContent_shouldPropagateCrossAxisUnconsumedFlings() {
         // Arrange
         var postFlingVelocity = Velocity.Zero
@@ -194,6 +253,66 @@
         assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0f)
     }
 
+    @OptIn(ExperimentalFoundationApi::class)
+    @Test
+    fun nestedScrollContent_shouldEnsurePagerIsSettled_WhenDirectionChanges() {
+        // Arrange
+        val lazyListState = LazyListState(9)
+        var touchSlop = 0f
+        createPager(pageCount = { DefaultPageCount }) {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            LazyList(
+                modifier = Modifier.fillMaxSize(),
+                contentPadding = PaddingValues(0.dp),
+                flingBehavior = ScrollableDefaults.flingBehavior(),
+                isVertical = vertical, // scrollable content on the same direction as pager
+                reverseLayout = false,
+                state = lazyListState,
+                userScrollEnabled = true,
+                verticalArrangement = Arrangement.Top,
+                horizontalArrangement = Arrangement.Start,
+                verticalAlignment = Alignment.Top,
+                horizontalAlignment = Alignment.Start
+            ) {
+                items(10) {
+                    Box(modifier = Modifier.size(100.dp)) {
+                        BasicText(text = it.toString())
+                    }
+                }
+            }
+        }
+
+        val forwardDelta = pagerSize * 0.4f * scrollForwardSign.toFloat()
+        val firstLazyListItem = lazyListState.firstVisibleItemIndex
+        val firstLazyListItemOffset = lazyListState.firstVisibleItemScrollOffset
+        rule.onNodeWithTag(TestTag).performTouchInput {
+            down(center)
+            val toMove = forwardDelta + touchSlop * scrollForwardSign.toFloat()
+            moveBy(if (vertical) Offset(x = 0f, y = toMove) else Offset(x = toMove, y = 0f))
+        }
+
+        // Assert: Inner list won't consume scroll and pager moved
+        assertThat(abs(pagerState.currentPageOffsetFraction - 0.4f)).isLessThan(0.001f)
+        assertThat(lazyListState.firstVisibleItemScrollOffset).isEqualTo(firstLazyListItemOffset)
+        assertThat(lazyListState.firstVisibleItemIndex).isEqualTo(firstLazyListItem)
+
+        rule.onNodeWithTag(TestTag).performTouchInput {
+            moveBy(
+                if (vertical) Offset(x = 0f, y = -forwardDelta / 2)
+                else Offset(x = -forwardDelta / 2, y = 0f)
+            )
+        }
+
+        // assert: pager moved, but list is still at 0 after direction change
+        assertThat(abs(pagerState.currentPageOffsetFraction - 0.2f)).isLessThan(0.001f)
+        assertThat(lazyListState.firstVisibleItemScrollOffset).isEqualTo(firstLazyListItemOffset)
+        assertThat(lazyListState.firstVisibleItemIndex).isEqualTo(firstLazyListItem)
+
+        rule.onNodeWithTag(TestTag).performTouchInput {
+            up()
+        }
+    }
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
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 fb5dc19..2eb2f14c 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
@@ -70,6 +70,7 @@
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastForEach
 import kotlin.math.abs
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -625,7 +626,13 @@
             val preConsumedByParent = nestedScrollDispatcher
                 .dispatchPreFling(velocity)
             val available = velocity - preConsumedByParent
-            val velocityLeft = doFlingAnimation(available)
+
+            val velocityLeft = try {
+                doFlingAnimation(available)
+            } catch (exception: CancellationException) {
+                available
+            }
+
             val consumedPost =
                 nestedScrollDispatcher.dispatchPostFling(
                     (available - velocityLeft),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index d92ad20..460d75a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -994,7 +994,11 @@
     private val resolvedSlots: LazyStaggeredGridSlots,
     private val measuredItemFactory: MeasuredItemFactory,
 ) {
-    private fun childConstraints(slot: Int, span: Int): Constraints {
+    private fun childConstraints(requestedSlot: Int, requestedSpan: Int): Constraints {
+        val slotCount = resolvedSlots.sizes.size
+        val slot = requestedSlot.coerceAtMost(slotCount - 1)
+        val span = requestedSpan.coerceAtMost(slotCount - slot)
+
         // resolved slots contain [offset, size] pair per each slot.
         val crossAxisSize = if (span == 1) {
             resolvedSlots.sizes[slot]
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 96e93f6..d64b834 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -54,10 +54,12 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
+import kotlin.math.abs
 import kotlin.math.absoluteValue
 import kotlin.math.ceil
 import kotlin.math.floor
 import kotlin.math.sign
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.launch
 
 /**
@@ -115,9 +117,9 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
-        Orientation.Horizontal
-    ),
+    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
+        PagerDefaults.pageNestedScrollConnection(state, Orientation.Horizontal)
+    },
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -223,9 +225,9 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
-        Orientation.Horizontal
-    ),
+    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
+        PagerDefaults.pageNestedScrollConnection(state, Orientation.Horizontal)
+    },
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -301,9 +303,9 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
-        Orientation.Vertical
-    ),
+    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
+        PagerDefaults.pageNestedScrollConnection(state, Orientation.Vertical)
+    },
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -408,9 +410,9 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
-        Orientation.Vertical
-    ),
+    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
+        PagerDefaults.pageNestedScrollConnection(state, Orientation.Vertical)
+    },
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -565,18 +567,17 @@
     }
 
     /**
-     * The default implementation of Pager's pageNestedScrollConnection. All fling scroll deltas
-     * will be consumed by the Pager.
+     * The default implementation of Pager's pageNestedScrollConnection.
      *
+     * @param state state of the pager
      * @param orientation The orientation of the pager. This will be used to determine which
-     * direction it will consume everything. The other direction will not be consumed.
+     * direction the nested scroll connection will operate and react on.
      */
-    fun pageNestedScrollConnection(orientation: Orientation): NestedScrollConnection {
-        return if (orientation == Orientation.Horizontal) {
-            ConsumeHorizontalFlingNestedScrollConnection
-        } else {
-            ConsumeVerticalFlingNestedScrollConnection
-        }
+    fun pageNestedScrollConnection(
+        state: PagerState,
+        orientation: Orientation
+    ): NestedScrollConnection {
+        return DefaultPagerNestedScrollConnection(state, orientation)
     }
 }
 
@@ -798,12 +799,11 @@
     }
 }
 
-private val ConsumeHorizontalFlingNestedScrollConnection =
-    ConsumeAllFlingOnDirection(Orientation.Horizontal)
-private val ConsumeVerticalFlingNestedScrollConnection =
-    ConsumeAllFlingOnDirection(Orientation.Vertical)
-
-private class ConsumeAllFlingOnDirection(val orientation: Orientation) : NestedScrollConnection {
+@OptIn(ExperimentalFoundationApi::class)
+private class DefaultPagerNestedScrollConnection(
+    val state: PagerState,
+    val orientation: Orientation
+) : NestedScrollConnection {
 
     fun Velocity.consumeOnOrientation(orientation: Orientation): Velocity {
         return if (orientation == Orientation.Vertical) {
@@ -821,15 +821,50 @@
         }
     }
 
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        return if (
+        // rounding error and drag only
+            source == NestedScrollSource.Drag && abs(state.currentPageOffsetFraction) > 0e-6
+        ) {
+            // find the current and next page (in the direction of dragging)
+            val currentPageOffset = state.currentPageOffsetFraction * state.pageSize
+            val pageAvailableSpace = state.layoutInfo.pageSize + state.layoutInfo.pageSpacing
+            val nextClosestPageOffset =
+                currentPageOffset + pageAvailableSpace * -sign(state.currentPageOffsetFraction)
+
+            val minBound: Float
+            val maxBound: Float
+            // build min and max bounds in absolute coordinates for nested scroll
+            if (state.currentPageOffsetFraction > 0f) {
+                minBound = nextClosestPageOffset
+                maxBound = currentPageOffset
+            } else {
+                minBound = currentPageOffset
+                maxBound = nextClosestPageOffset
+            }
+
+            val delta = if (orientation == Orientation.Horizontal) available.x else available.y
+            val coerced = delta.coerceIn(minBound, maxBound)
+            // dispatch and return reversed as usual
+            val consumed = -state.dispatchRawDelta(-coerced)
+            available.copy(
+                x = if (orientation == Orientation.Horizontal) consumed else available.x,
+                y = if (orientation == Orientation.Vertical) consumed else available.y,
+            )
+        } else {
+            Offset.Zero
+        }
+    }
+
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
         source: NestedScrollSource
     ): Offset {
-        return when (source) {
-            NestedScrollSource.Fling -> available.consumeOnOrientation(orientation)
-            else -> Offset.Zero
+        if (source == NestedScrollSource.Fling && available != Offset.Zero) {
+            throw CancellationException()
         }
+        return Offset.Zero
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
index edfbac3..6986de3 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
@@ -32,7 +32,7 @@
         slotExpect(
             name = "SpacerLike",
             noMoreGroupsThan = 5,
-            noMoreSlotsThan = 9,
+            noMoreSlotsThan = 10,
         ) {
             SpacerLike(Modifier)
         }
@@ -43,7 +43,7 @@
         slotExpect(
             name = "ColumnLike",
             noMoreGroupsThan = 6,
-            noMoreSlotsThan = 8,
+            noMoreSlotsThan = 9,
         ) {
             ColumnLike { }
         }
@@ -65,7 +65,7 @@
         slotExpect(
             name = "TextLike",
             noMoreGroupsThan = 9,
-            noMoreSlotsThan = 13
+            noMoreSlotsThan = 14
         ) {
             BasicTextLike("")
         }
@@ -76,7 +76,7 @@
         slotExpect(
             name = "CheckboxLike",
             noMoreGroupsThan = 12,
-            noMoreSlotsThan = 20
+            noMoreSlotsThan = 21
         ) {
             CheckboxLike(checked = false,  })
         }
@@ -84,7 +84,7 @@
 }
 
 // The following are a sketch of how compose ui uses composition to produce some important
-// composable functions. These are derived from the implementation as of Oct 2022.
+// composable functions. These are derived from the implementation as of May 2023.
 
 // The slot usage should be validated against the actual usage in GroupSizeTests in the
 // integration-tests periodically to avoid these skewing too far.
@@ -100,6 +100,7 @@
 
 private object ViewHelper {
     val Constructor = ::View
+    val SetCompositeKeyHash: View.(Int) -> Unit = { attributes["compositeKeyHash"] = it }
     val SetModifier: View.(Modifier) -> Unit = { attributes["modifier"] = it }
     val SetMeasurePolicy: View.(MeasurePolicy) -> Unit = { attributes["measurePolicy"] = it }
     val SetDensity: View.(Int) -> Unit = { attributes["density"] = it }
@@ -113,12 +114,14 @@
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
     val viewConfiguration = LocalViewConfiguration.current
     ReusableComposeNode<View, Applier<Any>>(
         factory = ViewHelper.Constructor,
         update = {
+            set(compositeKeyHash, ViewHelper.SetCompositeKeyHash)
             set(modifier, ViewHelper.SetModifier)
             set(measurePolicy, ViewHelper.SetMeasurePolicy)
             set(density, ViewHelper.SetDensity)
@@ -132,12 +135,14 @@
 @Composable
 @NonRestartableComposable
 private fun LayoutLike(modifier: Modifier, measurePolicy: MeasurePolicy) {
+    val compositeKeyHash = currentCompositeKeyHash
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
     val viewConfiguration = LocalViewConfiguration.current
     ReusableComposeNode<View, Applier<Any>>(
         factory = ViewHelper.Constructor,
         update = {
+            set(compositeKeyHash, ViewHelper.SetCompositeKeyHash)
             set(modifier, ViewHelper.SetModifier)
             set(measurePolicy, ViewHelper.SetMeasurePolicy)
             set(density, ViewHelper.SetDensity)
@@ -647,6 +652,7 @@
     return stringOf(this, "")
 }
 
+@Suppress("ConstPropertyName")
 private const val MarkerGroup = -340126117
 
 private fun findMarkerGroup(compositionData: CompositionData): CompositionGroup {
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 03d8bf8..a3d01d1 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2481,18 +2481,21 @@
 package androidx.compose.ui.node {
 
   @kotlin.PublishedApi internal interface ComposeUiNode {
+    method public int getCompositeKeyHash();
     method public androidx.compose.runtime.CompositionLocalMap getCompositionLocalMap();
     method public androidx.compose.ui.unit.Density getDensity();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public androidx.compose.ui.layout.MeasurePolicy getMeasurePolicy();
     method public androidx.compose.ui.Modifier getModifier();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
+    method public void setCompositeKeyHash(int);
     method public void setCompositionLocalMap(androidx.compose.runtime.CompositionLocalMap);
     method public void setDensity(androidx.compose.ui.unit.Density);
     method public void setLayoutDirection(androidx.compose.ui.unit.LayoutDirection);
     method public void setMeasurePolicy(androidx.compose.ui.layout.MeasurePolicy);
     method public void setModifier(androidx.compose.ui.Modifier);
     method public void setViewConfiguration(androidx.compose.ui.platform.ViewConfiguration);
+    property public abstract int compositeKeyHash;
     property public abstract androidx.compose.runtime.CompositionLocalMap compositionLocalMap;
     property public abstract androidx.compose.ui.unit.Density density;
     property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
@@ -2504,6 +2507,7 @@
 
   public static final class ComposeUiNode.Companion {
     method public kotlin.jvm.functions.Function0<androidx.compose.ui.node.ComposeUiNode> getConstructor();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,java.lang.Integer,kotlin.Unit> getSetCompositeKeyHash();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.unit.Density,kotlin.Unit> getSetDensity();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> getSetLayoutDirection();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.layout.MeasurePolicy,kotlin.Unit> getSetMeasurePolicy();
@@ -2512,6 +2516,7 @@
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.platform.ViewConfiguration,kotlin.Unit> getSetViewConfiguration();
     method public kotlin.jvm.functions.Function0<androidx.compose.ui.node.ComposeUiNode> getVirtualConstructor();
     property public final kotlin.jvm.functions.Function0<androidx.compose.ui.node.ComposeUiNode> Constructor;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,java.lang.Integer,kotlin.Unit> SetCompositeKeyHash;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.unit.Density,kotlin.Unit> SetDensity;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> SetLayoutDirection;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.ComposeUiNode,androidx.compose.ui.layout.MeasurePolicy,kotlin.Unit> SetMeasurePolicy;
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
index b677ada..d64b638 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
@@ -24,9 +24,11 @@
 import androidx.compose.runtime.ReusableComposeNode
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.currentCompositeKeyHash
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -37,6 +39,12 @@
 import androidx.compose.ui.layout.materializerOfWithCompositionLocalInjection
 import androidx.compose.ui.materializeWithCompositionLocalInjectionInternal
 import androidx.compose.ui.node.ComposeUiNode
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetDensity
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetLayoutDirection
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetMeasurePolicy
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetModifier
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetViewConfiguration
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.LayoutModifierNode
@@ -292,6 +300,7 @@
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
     val viewConfiguration = LocalViewConfiguration.current
@@ -299,10 +308,12 @@
     ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
-            set(density, ComposeUiNode.SetDensity)
-            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
-            set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
+            set(measurePolicy, SetMeasurePolicy)
+            set(density, SetDensity)
+            set(layoutDirection, SetLayoutDirection)
+            set(viewConfiguration, SetViewConfiguration)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         },
         // The old version of Layout called a function called "materializerOf". The function below
         // has the same JVM signature as that function used to have, so the code that this source
@@ -320,6 +331,7 @@
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
     val viewConfiguration = LocalViewConfiguration.current
@@ -331,11 +343,13 @@
     ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
-            set(density, ComposeUiNode.SetDensity)
-            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
-            set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
-            set(materialized, ComposeUiNode.SetModifier)
+            set(measurePolicy, SetMeasurePolicy)
+            set(density, SetDensity)
+            set(layoutDirection, SetLayoutDirection)
+            set(viewConfiguration, SetViewConfiguration)
+            set(materialized, SetModifier)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         },
     )
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
index 0b0714d..3e5cb7f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalComposeUiApi::class)
-
 package androidx.compose.ui.modifier
 
 import androidx.compose.runtime.Applier
@@ -792,6 +790,7 @@
         rule.setContent {
             ReusableContentHost(active) {
                 ReusableContent(0) {
+                    @OptIn(ExperimentalComposeUiApi::class)
                     Layout(
                         modifier = Modifier
                             .modifierLocalProvider(key) { providedValue }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositeKeyHashTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositeKeyHashTest.kt
new file mode 100644
index 0000000..a58e3c4
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositeKeyHashTest.kt
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import android.widget.TextView
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.key
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.MultiMeasureLayout
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalComposeUiApi::class)
+class CompositeKeyHashTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun nonZeroCompositeKeyHash() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        val compositeKeyHash = rule.runOnIdle { node.requireLayoutNode().compositeKeyHash }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun parentAndChildLayoutNodesHaveDifferentCompositeKeyHashes() {
+        // Arrange.
+        val (parent, child) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(parent)) {
+                Box(Modifier.elementOf(child))
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val parentCompositeKeyHash = parent.requireLayoutNode().compositeKeyHash
+        val childCompositeKeyHash = child.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(parentCompositeKeyHash).isNotEqualTo(childCompositeKeyHash)
+    }
+
+    @Test
+    fun differentChildrenHaveSameCompositeKeyHashes_Row() {
+        // Arrange.
+        val (node1, node2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Row {
+                Box(Modifier.elementOf(node1))
+                Box(Modifier.elementOf(node2))
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositeKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositeKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositeKeyHash1).isEqualTo(compositeKeyHash2)
+    }
+
+    @Test
+    fun differentChildrenWithKeyHaveDifferentCompositeKeyHashes_Row() {
+        // Arrange.
+        val (node1, node2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Row {
+                key(1) {
+                    Box(Modifier.elementOf(node1))
+                }
+                key(2) {
+                    Box(Modifier.elementOf(node2))
+                }
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositeKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositeKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositeKeyHash1).isNotEqualTo(compositeKeyHash2)
+    }
+    @Test
+    fun differentChildrenHaveSameCompositeKeyHashes_Box() {
+        // Arrange.
+        val (node1, node2) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box {
+                Box(Modifier.elementOf(node1))
+                Box(Modifier.elementOf(node2))
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositeKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositeKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositeKeyHash1).isEqualTo(compositeKeyHash2)
+    }
+
+    @Test
+    fun differentChildrenWithKeysHaveDifferentCompositeKeyHashes_Box() {
+        // Arrange.
+        val (node1, node2) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box {
+                key(1) {
+                    Box(Modifier.elementOf(node1))
+                }
+                key(2) {
+                    Box(Modifier.elementOf(node2))
+                }
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositeKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositeKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositeKeyHash1).isNotEqualTo(compositeKeyHash2)
+    }
+
+    @Test
+    fun differentChildrenInLazyColumn_item() {
+        // Arrange.
+        val (node1, node2) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            LazyColumn {
+                item {
+                    Box(Modifier.elementOf(node1))
+                }
+                item {
+                    Box(Modifier.elementOf(node2))
+                }
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositeKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositeKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositeKeyHash1).isNotEqualTo(compositeKeyHash2)
+    }
+
+    @Test
+    fun differentChildrenInLazyColumn_Items() {
+        // Arrange.
+        val (node1, node2) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            LazyColumn {
+                items(2) {
+                    Box(Modifier.elementOf(if (it == 0) node1 else node2))
+                }
+            }
+        }
+
+        // Act.
+        rule.waitForIdle()
+        val compositionKeyHash1 = node1.requireLayoutNode().compositeKeyHash
+        val compositionKeyHash2 = node2.requireLayoutNode().compositeKeyHash
+
+        // Assert.
+        assertThat(compositionKeyHash1).isNotEqualTo(compositionKeyHash2)
+    }
+
+    @Test
+    fun text() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            BasicText(
+                text = "text",
+                modifier = Modifier.elementOf(node),
+            )
+        }
+
+        // Act.
+        val compositeKeyHash = rule.runOnIdle { node.requireLayoutNode().compositeKeyHash }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun androidView() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            AndroidView(
+                factory = { TextView(it) },
+                modifier = Modifier.elementOf(node)
+            )
+        }
+
+        // Act.
+        val compositeKeyHash = rule.runOnIdle { node.requireLayoutNode().compositeKeyHash }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun androidView_noOnReset() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            AndroidView(
+                factory = { TextView(it) },
+                modifier = Modifier.elementOf(node),
+                >
+            )
+        }
+
+        // Act.
+        val compositeKeyHash = rule.runOnIdle { node.requireLayoutNode().compositeKeyHash }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun androidView_withOnReset() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            AndroidView(
+                factory = { TextView(it) },
+                modifier = Modifier.elementOf(node),
+                 }
+            )
+        }
+
+        // Act.
+        val compositeKeyHash = rule.runOnIdle { node.requireLayoutNode().compositeKeyHash }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun Layout1() {
+        // Arrange.
+        var compositeKeyHash = 0
+        rule.setContent {
+            Layout1 { compositeKeyHash = it }
+        }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun Layout2() { // Add other overloads of Layout here.
+        // Arrange.
+        var compositeKeyHash = 0
+        rule.setContent {
+            Layout2 { compositeKeyHash = it }
+        }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun Layout3() { // Add other overloads of Layout here.
+        // Arrange.
+        var compositeKeyHash = 0
+        rule.setContent {
+            Layout3 { compositeKeyHash = it }
+        }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Test
+    fun Layout4() { // Add other overloads of Layout here.
+        // Arrange.
+        var compositeKeyHash = 0
+        rule.setContent {
+            Layout4 { compositeKeyHash = it }
+        }
+
+        // Assert.
+        assertThat(compositeKeyHash).isNotEqualTo(0)
+    }
+
+    @Composable
+    private fun Layout1(onSetCompositionKeyHash: (Int) -> Unit) {
+        val node = remember { object : Modifier.Node() {} }
+        Layout(
+            measurePolicy = { measurables, constraints ->
+                measurables.forEach { it.measure(constraints) }
+                layout(0, 0) {}
+            },
+            modifier = Modifier.elementOf(node)
+        )
+        SideEffect {
+            onSetCompositionKeyHash(node.requireLayoutNode().compositeKeyHash)
+        }
+    }
+
+    @Composable
+    private fun Layout2(onSetCompositionKeyHash: (Int) -> Unit) {
+        val node = remember { object : Modifier.Node() {} }
+        Layout(
+            contents = listOf({}, {}),
+            measurePolicy = { measurables, constraints ->
+                measurables.forEach {
+                    it.forEach { measurable ->
+                        measurable.measure(constraints)
+                    }
+                }
+                layout(0, 0) {}
+            },
+            modifier = Modifier.elementOf(node)
+        )
+        SideEffect {
+            onSetCompositionKeyHash.invoke(node.requireLayoutNode().compositeKeyHash)
+        }
+    }
+
+    @Composable
+    private fun Layout3(onSetCompositionKeyHash: (Int) -> Unit) {
+        val node = remember { object : Modifier.Node() {} }
+        Layout(
+            content = { },
+            measurePolicy = { measurables, constraints ->
+                measurables.forEach { it.measure(constraints) }
+                layout(0, 0) {}
+            },
+            modifier = Modifier.elementOf(node)
+        )
+        SideEffect {
+            onSetCompositionKeyHash.invoke(node.requireLayoutNode().compositeKeyHash)
+        }
+    }
+
+    @Composable
+    private fun Layout4(onSetCompositionKeyHash: (Int) -> Unit) {
+        val node = remember { object : Modifier.Node() {} }
+        @Suppress("DEPRECATION")
+        MultiMeasureLayout(
+            content = { },
+            measurePolicy = { measurables, constraints ->
+                measurables.forEach { it.measure(constraints) }
+                layout(0, 0) {}
+            },
+            modifier = Modifier.elementOf(node)
+        )
+        SideEffect {
+            onSetCompositionKeyHash.invoke(node.requireLayoutNode().compositeKeyHash)
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
index e47c66b..86c5c68 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
@@ -32,11 +32,14 @@
 import androidx.compose.runtime.rememberCompositionContext
 import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
 import androidx.compose.runtime.saveable.SaveableStateRegistry
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.materialize
-import androidx.compose.ui.node.ComposeUiNode
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetResolvedCompositionLocals
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.UiApplier
 import androidx.compose.ui.platform.LocalContext
@@ -204,6 +207,7 @@
     onRelease: (T) -> Unit = NoOpUpdate,
     update: (T) -> Unit = NoOpUpdate
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val materializedModifier = currentComposer.materialize(modifier)
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
@@ -222,6 +226,7 @@
             update = {
                 updateViewHolderParams<T>(
                     modifier = materializedModifier,
+                    compositeKeyHash = compositeKeyHash,
                     density = density,
                     lifecycleOwner = lifecycleOwner,
                     savedStateRegistryOwner = savedStateRegistryOwner,
@@ -239,6 +244,7 @@
             update = {
                 updateViewHolderParams<T>(
                     modifier = materializedModifier,
+                    compositeKeyHash = compositeKeyHash,
                     density = density,
                     lifecycleOwner = lifecycleOwner,
                     savedStateRegistryOwner = savedStateRegistryOwner,
@@ -256,31 +262,32 @@
 private fun <T : View> createAndroidViewNodeFactory(
     factory: (Context) -> T
 ): () -> LayoutNode {
+    val compositeKeyHash = currentCompositeKeyHash
     val context = LocalContext.current
     val parentReference = rememberCompositionContext()
     val stateRegistry = LocalSaveableStateRegistry.current
-    val stateKey = currentCompositeKeyHash.toString()
 
     return {
-        ViewFactoryHolder<T>(
+        ViewFactoryHolder(
             context = context,
             factory = factory,
             parentContext = parentReference,
             saveStateRegistry = stateRegistry,
-            saveStateKey = stateKey
+            compositeKeyHash = compositeKeyHash
         ).layoutNode
     }
 }
 
 private fun <T : View> Updater<LayoutNode>.updateViewHolderParams(
     modifier: Modifier,
+    compositeKeyHash: Int,
     density: Density,
     lifecycleOwner: LifecycleOwner,
     savedStateRegistryOwner: SavedStateRegistryOwner,
     layoutDirection: LayoutDirection,
     compositionLocalMap: CompositionLocalMap
 ) {
-    set(compositionLocalMap, ComposeUiNode.SetResolvedCompositionLocals)
+    set(compositionLocalMap, SetResolvedCompositionLocals)
     set(modifier) { requireViewFactoryHolder<T>().modifier = it }
     set(density) { requireViewFactoryHolder<T>().density = it }
     set(lifecycleOwner) { requireViewFactoryHolder<T>().lifecycleOwner = it }
@@ -293,10 +300,13 @@
             LayoutDirection.Rtl -> android.util.LayoutDirection.RTL
         }
     }
+    @OptIn(ExperimentalComposeUiApi::class)
+    set(compositeKeyHash, SetCompositeKeyHash)
 }
 
 @Suppress("UNCHECKED_CAST")
 private fun <T : View> LayoutNode.requireViewFactoryHolder(): ViewFactoryHolder<T> {
+    @OptIn(InternalComposeUiApi::class)
     return checkNotNull(interopViewFactoryHolder) as ViewFactoryHolder<T>
 }
 
@@ -308,30 +318,33 @@
 internal class ViewFactoryHolder<T : View> private constructor(
     context: Context,
     parentContext: CompositionContext? = null,
-    val typedView: T,
+    private val typedView: T,
     // NestedScrollDispatcher that will be passed/used for nested scroll interop
     val dispatcher: NestedScrollDispatcher = NestedScrollDispatcher(),
     private val saveStateRegistry: SaveableStateRegistry?,
-    private val saveStateKey: String
-) : AndroidViewHolder(context, parentContext, dispatcher, typedView), ViewRootForInspector {
+    private val compositeKeyHash: Int,
+) : AndroidViewHolder(context, parentContext, compositeKeyHash, dispatcher, typedView),
+    ViewRootForInspector {
 
     constructor(
         context: Context,
         factory: (Context) -> T,
         parentContext: CompositionContext? = null,
         saveStateRegistry: SaveableStateRegistry?,
-        saveStateKey: String
+        compositeKeyHash: Int
     ) : this(
         context = context,
         typedView = factory(context),
         parentContext = parentContext,
         saveStateRegistry = saveStateRegistry,
-        saveStateKey = saveStateKey,
+        compositeKeyHash = compositeKeyHash,
     )
 
     override val viewRoot: View get() = this
 
-    private var saveableRegistryEntry: SaveableStateRegistry.Entry? = null
+    private val saveStateKey: String
+
+    private var savableRegistryEntry: SaveableStateRegistry.Entry? = null
         set(value) {
             field?.unregister()
             field = value
@@ -339,6 +352,7 @@
 
     init {
         clipChildren = false
+        saveStateKey = compositeKeyHash.toString()
 
         @Suppress("UNCHECKED_CAST")
         val savedState = saveStateRegistry
@@ -370,7 +384,7 @@
 
     private fun registerSaveStateProvider() {
         if (saveStateRegistry != null) {
-            saveableRegistryEntry = saveStateRegistry.registerProvider(saveStateKey) {
+            savableRegistryEntry = saveStateRegistry.registerProvider(saveStateKey) {
                 SparseArray<Parcelable>().apply {
                     typedView.saveHierarchyState(this)
                 }
@@ -379,6 +393,6 @@
     }
 
     private fun unregisterSaveStateProvider() {
-        saveableRegistryEntry = null
+        savableRegistryEntry = null
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index dee4515..b646acd 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -28,6 +28,7 @@
 import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.snapshots.SnapshotStateObserver
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
@@ -73,6 +74,7 @@
 internal open class AndroidViewHolder(
     context: Context,
     parentContext: CompositionContext?,
+    private val compositeKeyHash: Int,
     private val dispatcher: NestedScrollDispatcher,
     /**
      * The view hosted by this holder.
@@ -323,6 +325,7 @@
     val layoutNode: LayoutNode = run {
         // Prepare layout node that proxies measure and layout passes to the View.
         val layoutNode = LayoutNode()
+        @OptIn(InternalComposeUiApi::class)
         layoutNode.interopViewFactoryHolder = this@AndroidViewHolder
 
         val coreModifier = Modifier
@@ -341,6 +344,7 @@
                 // these cases, we need to inform the View.
                 layoutAccordingTo(layoutNode)
             }
+        layoutNode.compositeKeyHash = compositeKeyHash
         layoutNode.modifier = modifier.then(coreModifier)
          layoutNode.modifier = it.then(coreModifier) }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 813a516..ff4713b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -23,13 +23,19 @@
 import androidx.compose.runtime.ReusableComposeNode
 import androidx.compose.runtime.SkippableUpdater
 import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.currentCompositeKeyHash
 import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.materialize
 import androidx.compose.ui.materializeWithCompositionLocalInjectionInternal
 import androidx.compose.ui.node.ComposeUiNode
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetMeasurePolicy
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetModifier
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetResolvedCompositionLocals
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
@@ -69,12 +75,15 @@
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val localMap = currentComposer.currentCompositionLocalMap
     ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
-            set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
+            set(measurePolicy, SetMeasurePolicy)
+            set(localMap, SetResolvedCompositionLocals)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         },
         skippableUpdate = materializerOf(modifier),
         content = content
@@ -111,14 +120,17 @@
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val materialized = currentComposer.materialize(modifier)
     val localMap = currentComposer.currentCompositionLocalMap
     ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
-            set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
-            set(materialized, ComposeUiNode.SetModifier)
+            set(measurePolicy, SetMeasurePolicy)
+            set(localMap, SetResolvedCompositionLocals)
+            set(materialized, SetModifier)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         },
     )
 }
@@ -166,9 +178,13 @@
     contents: List<@Composable @UiComposable () -> Unit>
 ): @Composable @UiComposable () -> Unit = {
     contents.fastForEach { content ->
+        val compositeKeyHash = currentCompositeKeyHash
         ReusableComposeNode<ComposeUiNode, Applier<Any>>(
             factory = ComposeUiNode.VirtualConstructor,
-            update = {},
+            update = {
+                @OptIn(ExperimentalComposeUiApi::class)
+                set(compositeKeyHash, SetCompositeKeyHash)
+            },
             content = content
         )
     }
@@ -184,9 +200,12 @@
 internal fun materializerOf(
     modifier: Modifier
 ): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
+    val compositeKeyHash = currentCompositeKeyHash
     val materialized = currentComposer.materialize(modifier)
     update {
-        set(materialized, ComposeUiNode.SetModifier)
+        set(materialized, SetModifier)
+        @OptIn(ExperimentalComposeUiApi::class)
+        set(compositeKeyHash, SetCompositeKeyHash)
     }
 }
 
@@ -204,9 +223,12 @@
 internal fun materializerOfWithCompositionLocalInjection(
     modifier: Modifier
 ): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
+    val compositeKeyHash = currentCompositeKeyHash
     val materialized = currentComposer.materializeWithCompositionLocalInjectionInternal(modifier)
     update {
-        set(materialized, ComposeUiNode.SetModifier)
+        set(materialized, SetModifier)
+        @OptIn(ExperimentalComposeUiApi::class)
+        set(compositeKeyHash, SetCompositeKeyHash)
     }
 }
 
@@ -222,17 +244,20 @@
     content: @Composable @UiComposable () -> Unit,
     measurePolicy: MeasurePolicy
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val materialized = currentComposer.materialize(modifier)
     val localMap = currentComposer.currentCompositionLocalMap
 
     ReusableComposeNode<LayoutNode, Applier<Any>>(
         factory = LayoutNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
-            set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
+            set(measurePolicy, SetMeasurePolicy)
+            set(localMap, SetResolvedCompositionLocals)
             @Suppress("DEPRECATION")
             init { this.canMultiMeasure = true }
-            set(materialized, ComposeUiNode.SetModifier)
+            set(materialized, SetModifier)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         },
         content = content
     )
@@ -277,8 +302,8 @@
  */
 internal class DefaultIntrinsicMeasurable(
     val measurable: IntrinsicMeasurable,
-    val minMax: IntrinsicMinMax,
-    val widthHeight: IntrinsicWidthHeight
+    private val minMax: IntrinsicMinMax,
+    private val widthHeight: IntrinsicWidthHeight
 ) : Measurable {
     override val parentData: Any?
         get() = measurable.parentData
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 619812e..f5146b8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -25,6 +25,7 @@
 import androidx.compose.runtime.ReusableContentHost
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.currentCompositeKeyHash
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -37,7 +38,9 @@
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
 import androidx.compose.ui.materialize
-import androidx.compose.ui.node.ComposeUiNode
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetModifier
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetResolvedCompositionLocals
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNode.LayoutState
 import androidx.compose.ui.node.LayoutNode.UsageByParent
@@ -104,6 +107,7 @@
     modifier: Modifier = Modifier,
     measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
 ) {
+    val compositeKeyHash = currentCompositeKeyHash
     val compositionContext = rememberCompositionContext()
     val materialized = currentComposer.materialize(modifier)
     val localMap = currentComposer.currentCompositionLocalMap
@@ -113,8 +117,10 @@
             set(state, state.setRoot)
             set(compositionContext, state.setCompositionContext)
             set(measurePolicy, state.setMeasurePolicy)
-            set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
-            set(materialized, ComposeUiNode.SetModifier)
+            set(localMap, SetResolvedCompositionLocals)
+            set(materialized, SetModifier)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
         }
     )
     if (!currentComposer.skipping) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/TestModifierUpdater.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/TestModifierUpdater.kt
index 5bda8ad..bc53f49 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/TestModifierUpdater.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/TestModifierUpdater.kt
@@ -20,8 +20,11 @@
 import androidx.compose.runtime.Applier
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.currentCompositeKeyHash
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.node.ComposeUiNode
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
+import androidx.compose.ui.node.ComposeUiNode.Companion.SetMeasurePolicy
 import androidx.compose.ui.node.LayoutNode
 
 @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -45,13 +48,16 @@
 @Composable
 @Suppress("DEPRECATION_ERROR")
 fun TestModifierUpdaterLayout(onAttached: (TestModifierUpdater) -> Unit) {
+    val compositeKeyHash = currentCompositeKeyHash
     val measurePolicy = MeasurePolicy { _, constraints ->
         layout(constraints.maxWidth, constraints.maxHeight) {}
     }
     ComposeNode<LayoutNode, Applier<Any>>(
         factory = LayoutNode.Constructor,
         update = {
-            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
+            set(measurePolicy, SetMeasurePolicy)
+            @OptIn(ExperimentalComposeUiApi::class)
+            set(compositeKeyHash, SetCompositeKeyHash)
             init { onAttached(TestModifierUpdater(this)) }
         }
     )
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ComposeUiNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ComposeUiNode.kt
index c808fdb..f55bfb5 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ComposeUiNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ComposeUiNode.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.node
 
 import androidx.compose.runtime.CompositionLocalMap
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.platform.ViewConfiguration
@@ -34,6 +35,8 @@
     var modifier: Modifier
     var viewConfiguration: ViewConfiguration
     var compositionLocalMap: CompositionLocalMap
+    @ExperimentalComposeUiApi
+    var compositeKeyHash: Int
 
     /**
      * Object of pre-allocated lambdas used to make use with ComposeNode allocation-less.
@@ -51,5 +54,10 @@
             { this.layoutDirection = it }
         val SetViewConfiguration: ComposeUiNode.(ViewConfiguration) -> Unit =
             { this.viewConfiguration = it }
+        @get:ExperimentalComposeUiApi
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @ExperimentalComposeUiApi
+        val SetCompositeKeyHash: ComposeUiNode.(Int) -> Unit =
+            { this.compositeKeyHash = it }
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 574419a..13b3d42 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -64,6 +64,7 @@
 /**
  * Enable to log changes to the LayoutNode tree.  This logging is quite chatty.
  */
+@Suppress("ConstPropertyName")
 private const val DebugChanges = false
 
 private val DefaultDensity = Density(1f)
@@ -82,6 +83,7 @@
     // subcompose multiple times into the same LayoutNode and define offsets.
     private val isVirtual: Boolean = false,
     // The unique semantics ID that is used by all semantics modifiers attached to this LayoutNode.
+    // TODO(b/281907968): Implement this with a getter that returns the compositeKeyHash.
     override var semanticsId: Int = generateSemanticsId()
 ) : ComposeNodeLifecycleCallback,
     Remeasurement,
@@ -91,6 +93,12 @@
     InteroperableComposeUiNode,
     Owner.OnLayoutCompletedListener {
 
+    @set:ExperimentalComposeUiApi
+    @get:ExperimentalComposeUiApi
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @ExperimentalComposeUiApi
+    override var compositeKeyHash: Int = 0
+
     internal var isVirtualLookaheadRoot: Boolean = false
 
     /**
@@ -669,7 +677,6 @@
             density = value[LocalDensity]
             layoutDirection = value[LocalLayoutDirection]
             viewConfiguration = value[LocalViewConfiguration]
-            @OptIn(ExperimentalComposeUiApi::class)
             nodes.headToTail(Nodes.CompositionLocalConsumer) { modifierNode ->
                 val delegatedNode = modifierNode.node
                 if (delegatedNode.isAttached) {
@@ -818,7 +825,6 @@
     /**
      * The [Modifier] currently applied to this node.
      */
-    @OptIn(ExperimentalComposeUiApi::class)
     override var modifier: Modifier = Modifier
         set(value) {
             require(!isVirtual || modifier === Modifier) {
@@ -1028,7 +1034,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     private fun invalidateFocusOnAttach() {
         if (nodes.has(FocusTarget or FocusProperties or FocusEvent)) {
             nodes.headToTail {
@@ -1069,7 +1074,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     internal fun dispatchOnPositionedCallbacks() {
         if (layoutState != Idle || layoutPending || measurePending) {
             return // it hasn't yet been properly positioned, so don't make a call
@@ -1169,7 +1173,6 @@
      */
     internal fun markLookaheadLayoutPending() = layoutDelegate.markLookaheadLayoutPending()
 
-    @OptIn(ExperimentalComposeUiApi::class)
     fun invalidateSubtree(isRootOfInvalidation: Boolean = true) {
         if (isRootOfInvalidation) {
             parent?.invalidateLayer()
@@ -1204,7 +1207,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     override fun onLayoutComplete() {
         innerCoordinator.visitNodes(Nodes.LayoutAware) {
             it.onPlaced(innerCoordinator)
@@ -1235,7 +1237,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     private fun shouldInvalidateParentLayer(): Boolean {
         if (nodes.has(Nodes.Draw) && !nodes.has(Nodes.Layout)) return true
         nodes.headToTail {
diff --git a/core/core-ktx/src/main/java/androidx/core/os/Trace.kt b/core/core-ktx/src/main/java/androidx/core/os/Trace.kt
index f7f9462..da489ef 100644
--- a/core/core-ktx/src/main/java/androidx/core/os/Trace.kt
+++ b/core/core-ktx/src/main/java/androidx/core/os/Trace.kt
@@ -25,7 +25,7 @@
 @Deprecated(
     "Use androidx.tracing.Trace instead",
     replaceWith = ReplaceWith(
-        "trace(sectionName)",
+        "trace(sectionName, block)",
         imports = arrayOf("androidx.tracing.trace")
     )
 )
diff --git a/core/core/api/1.11.0-beta01.txt b/core/core/api/1.11.0-beta01.txt
index 0c385e3..128f0c4 100644
--- a/core/core/api/1.11.0-beta01.txt
+++ b/core/core/api/1.11.0-beta01.txt
@@ -1446,7 +1446,7 @@
     method @ColorInt public static int HSLToColor(float[]);
     method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
     method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
-    method @ColorInt public static int M3HCTtoColor(float, float, float);
+    method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
     method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
     method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
     method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
@@ -1460,7 +1460,7 @@
     method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
     method public static void colorToHSL(@ColorInt int, float[]);
     method public static void colorToLAB(@ColorInt int, double[]);
-    method public static void colorToM3HCT(@ColorInt int, float[]);
+    method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
     method public static void colorToXYZ(@ColorInt int, double[]);
     method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
     method public static int compositeColors(@ColorInt int, @ColorInt int);
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 0c385e3..128f0c4 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1446,7 +1446,7 @@
     method @ColorInt public static int HSLToColor(float[]);
     method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
     method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
-    method @ColorInt public static int M3HCTtoColor(float, float, float);
+    method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
     method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
     method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
     method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
@@ -1460,7 +1460,7 @@
     method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
     method public static void colorToHSL(@ColorInt int, float[]);
     method public static void colorToLAB(@ColorInt int, double[]);
-    method public static void colorToM3HCT(@ColorInt int, float[]);
+    method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
     method public static void colorToXYZ(@ColorInt int, double[]);
     method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
     method public static int compositeColors(@ColorInt int, @ColorInt int);
diff --git a/core/core/api/restricted_1.11.0-beta01.txt b/core/core/api/restricted_1.11.0-beta01.txt
index 29edb7b..f02ce1a 100644
--- a/core/core/api/restricted_1.11.0-beta01.txt
+++ b/core/core/api/restricted_1.11.0-beta01.txt
@@ -1506,7 +1506,7 @@
 package androidx.core.content.res {
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CamColor {
-    method public static void getM3HCTfromColor(@ColorInt int, float[]);
+    method public static void getM3HCTfromColor(@ColorInt int, @Size(3) float[]);
     method public static int toColor(@FloatRange(from=0.0, to=360.0) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100.0) float);
   }
 
@@ -1687,7 +1687,7 @@
     method @ColorInt public static int HSLToColor(float[]);
     method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
     method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
-    method @ColorInt public static int M3HCTtoColor(float, float, float);
+    method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
     method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
     method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
     method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
@@ -1701,7 +1701,7 @@
     method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
     method public static void colorToHSL(@ColorInt int, float[]);
     method public static void colorToLAB(@ColorInt int, double[]);
-    method public static void colorToM3HCT(@ColorInt int, float[]);
+    method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
     method public static void colorToXYZ(@ColorInt int, double[]);
     method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
     method public static int compositeColors(@ColorInt int, @ColorInt int);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 29edb7b..f02ce1a 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1506,7 +1506,7 @@
 package androidx.core.content.res {
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CamColor {
-    method public static void getM3HCTfromColor(@ColorInt int, float[]);
+    method public static void getM3HCTfromColor(@ColorInt int, @Size(3) float[]);
     method public static int toColor(@FloatRange(from=0.0, to=360.0) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100.0) float);
   }
 
@@ -1687,7 +1687,7 @@
     method @ColorInt public static int HSLToColor(float[]);
     method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
     method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
-    method @ColorInt public static int M3HCTtoColor(float, float, float);
+    method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
     method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
     method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
     method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
@@ -1701,7 +1701,7 @@
     method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
     method public static void colorToHSL(@ColorInt int, float[]);
     method public static void colorToLAB(@ColorInt int, double[]);
-    method public static void colorToM3HCT(@ColorInt int, float[]);
+    method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
     method public static void colorToXYZ(@ColorInt int, double[]);
     method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
     method public static int compositeColors(@ColorInt int, @ColorInt int);
diff --git a/core/core/src/main/java/androidx/core/content/res/CamColor.java b/core/core/src/main/java/androidx/core/content/res/CamColor.java
index 5aca7c5..9cd05c0 100644
--- a/core/core/src/main/java/androidx/core/content/res/CamColor.java
+++ b/core/core/src/main/java/androidx/core/content/res/CamColor.java
@@ -21,6 +21,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.Size;
 import androidx.core.graphics.ColorUtils;
 
 /**
@@ -170,6 +171,10 @@
      *
      * Get the values for M3HCT color from ARGB color.
      *
+     * HCT color space is a new color space proposed in Material Design 3
+     * @see
+     * <a href="https://developer.android.com/design/ui/mobile/guides/styles/color#about-color-spaces">About Color Spaces</a>
+     *
      *<ul>
      *<li>outM3HCT[0] is Hue in M3HCT [0, 360); invalid values are corrected.</li>
      *<li>outM3HCT[1] is Chroma in M3HCT [0, ?); Chroma may decrease because chroma has a
@@ -182,7 +187,7 @@
      *      Chroma, Tone).
      */
     public static void getM3HCTfromColor(@ColorInt int color,
-            @NonNull float[] outM3HCT) {
+            @NonNull @Size(3) float[] outM3HCT) {
         fromColorInViewingConditions(color, ViewingConditions.DEFAULT, null, outM3HCT);
         outM3HCT[2] = CamUtils.lStarFromInt(color);
     }
@@ -192,8 +197,8 @@
      * ViewingConditions in which the color was viewed. Prefer Cam.fromColor.
      */
     static void fromColorInViewingConditions(@ColorInt int color,
-            @NonNull ViewingConditions viewingConditions, @Nullable float[] outCamColor,
-            @NonNull float[] outM3HCT) {
+            @NonNull ViewingConditions viewingConditions, @Nullable @Size(7) float[] outCamColor,
+            @NonNull @Size(3) float[] outM3HCT) {
         // Transform ARGB int to XYZ, reusing outM3HCT array to avoid a new allocation.
         CamUtils.xyzFromInt(color, outM3HCT);
         float[] xyz = outM3HCT;
diff --git a/core/core/src/main/java/androidx/core/graphics/ColorUtils.java b/core/core/src/main/java/androidx/core/graphics/ColorUtils.java
index b709487..7d26837 100644
--- a/core/core/src/main/java/androidx/core/graphics/ColorUtils.java
+++ b/core/core/src/main/java/androidx/core/graphics/ColorUtils.java
@@ -25,6 +25,7 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.Size;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.CamColor;
 
@@ -683,7 +684,11 @@
     }
 
     /**
-     * Generate an ARGB color using M3HCT color parameters (Hue, Chroma, and Tone).
+     * Generate an ARGB color using M3HCT color parameters.
+     *
+     * HCT color space is a new color space proposed in Material Design 3
+     * @see
+     * <a href="https://developer.android.com/design/ui/mobile/guides/styles/color#about-color-spaces">About Color Spaces</a>
      *
      * @param hue is Hue in M3HCT [0, 360); invalid values are corrected.
      * @param chroma is Chroma in M3HCT [0, ?); Chroma may decrease because chroma has a
@@ -692,13 +697,19 @@
      */
     @SuppressWarnings("AcronymName")
     @ColorInt
-    public static int M3HCTtoColor(float hue, float chroma, float tone) {
+    public static int M3HCTToColor(@FloatRange(from = 0.0, to = 360, toInclusive = false) float hue,
+            @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false)
+            float chroma, @FloatRange(from = 0.0, to = 100) float tone) {
         return CamColor.toColor(hue, chroma, tone);
     }
 
     /**
      * Generate a M3HCT color from an ARGB color.
      *
+     * HCT color space is a new color space proposed in Material Design 3
+     * @see
+     * <a href="https://developer.android.com/design/ui/mobile/guides/styles/color#about-color-spaces">About Color Spaces</a>
+     *
      * <ul>
      * <li>outM3HCT[0] is Hue in M3HCT [0, 360); invalid values are corrected.</li>
      * <li>outM3HCT[1] is Chroma in M3HCT [0, ?); Chroma may decrease because chroma has a
@@ -711,7 +722,7 @@
      *                 Tone).
      */
     @SuppressWarnings("AcronymName")
-    public static void colorToM3HCT(@ColorInt int color, @NonNull float[] outM3HCT) {
+    public static void colorToM3HCT(@ColorInt int color, @NonNull @Size(3) float[] outM3HCT) {
         CamColor.getM3HCTfromColor(color, outM3HCT);
     }
 
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 5d6fef5..72d6e17 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -167,7 +167,6 @@
 WARN: Missing @param tag for parameter `variationSettings` of function androidx\.compose\.ui\.text\.font/AndroidFont/AndroidFont/\#androidx\.compose\.ui\.text\.font\.FontLoadingStrategy\#androidx\.compose\.ui\.text\.font\.AndroidFont\.TypefaceLoader\#androidx\.compose\.ui\.text\.font\.FontVariation\.Settings/PointingToDeclaration/
 WARN: Missing @param tag for parameter `motionScene` of function androidx\.constraintlayout\.compose//MotionCarousel/\#androidx\.constraintlayout\.compose\.MotionScene\#kotlin\.Int\#kotlin\.Int\#kotlin\.String\#kotlin\.String\#kotlin\.String\#kotlin\.Boolean\#kotlin\.Function[0-9]+\[androidx\.constraintlayout\.compose\.MotionCarouselScope,kotlin\.Unit\]/PointingToDeclaration/
 WARN: Missing @param tag for parameter `content` of function androidx\.glance\.appwidget//AndroidRemoteViews/\#android\.widget\.RemoteViews\#kotlin\.Int\#kotlin\.Function[0-9]+\[kotlin\.Unit\]/PointingToDeclaration/
-WARN: Missing @param tag for parameter `rendererName` of function androidx\.media[0-9]+\.exoplayer/ExoPlaybackException/createForRenderer/\#java\.lang\.Throwable\#java\.lang\.String\#int\#androidx\.media[0-9]+\.common\.Format\#int\#boolean\#int/PointingToDeclaration/
 WARN: Missing @param tag for parameter `output` of function androidx\.glance\.appwidget\.proto/LayoutProtoSerializer/writeTo/\#androidx\.glance\.appwidget\.proto\.LayoutProto\.LayoutConfig\#java\.io\.OutputStream/PointingToDeclaration/
 WARN: Missing @param tag for parameter `factory` of function androidx\.lifecycle\.viewmodel\.compose//viewModel/\#java\.lang\.Class\[TypeParam\(bounds=\[androidx\.lifecycle\.ViewModel\]\)\]\#androidx\.lifecycle\.ViewModelStoreOwner\#kotlin\.String\?\#androidx\.lifecycle\.ViewModelProvider\.Factory\?\#androidx\.lifecycle\.viewmodel\.CreationExtras/PointingToDeclaration/
 WARN: Failed to resolve `@see PagingSource\.invalidate`!
@@ -470,7 +469,6 @@
 WARN: Missing @param tag for parameter `c` of function androidx\.cursoradapter\.widget/ResourceCursorAdapter/ResourceCursorAdapter/\#android\.content\.Context\#int\#android\.database\.Cursor/PointingToDeclaration/
 WARN: Missing @param tag for parameter `listener` of function androidx\.customview\.poolingcontainer/PoolingContainer/addPoolingContainerListener/android\.view\.View\#androidx\.customview\.poolingcontainer\.PoolingContainerListener/PointingToDeclaration/
 WARN: Missing @param tag for parameter `listener` of function androidx\.customview\.poolingcontainer//addPoolingContainerListener/android\.view\.View\#androidx\.customview\.poolingcontainer\.PoolingContainerListener/PointingToDeclaration/
-WARN: Failed to resolve `@see <a href="https://www\.w[0-9]+\.org/TR/ttml[0-9]+/">Timed Text Markup Language [0-9]+ \(TTML[0-9]+\) \- [0-9]+\.[0-9]+\.[0-9]+</a>`!
 WARN: Missing @param tag for parameter `pointerId` of function androidx\.customview\.widget/ViewDragHelper/isEdgeTouched/\#int\#int/PointingToDeclaration/
 WARN: Missing @param tag for parameter `serializer` of function androidx\.datastore//dataStore/\#kotlin\.String\#androidx\.datastore\.core\.Serializer\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\#androidx\.datastore\.core\.handlers\.ReplaceFileCorruptionHandler\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\?\#kotlin\.Function[0-9]+\[android\.content\.Context,kotlin\.collections\.List\[androidx\.datastore\.core\.DataMigration\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\]\]\#kotlinx\.coroutines\.CoroutineScope/PointingToDeclaration/
 WARN: Missing @param tag for parameter `serializer` of function androidx\.datastore/DataStoreDelegateKt/dataStore/\#kotlin\.String\#androidx\.datastore\.core\.Serializer\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\#androidx\.datastore\.core\.handlers\.ReplaceFileCorruptionHandler\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\?\#kotlin\.Function[0-9]+\[android\.content\.Context,kotlin\.collections\.List\[androidx\.datastore\.core\.DataMigration\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]\]\]\#kotlinx\.coroutines\.CoroutineScope/PointingToDeclaration/
@@ -486,9 +484,6 @@
 WARN: Failed to resolve `@see <a href="https://developer\.android\.com/guide/topics/ui/drag\-drop">Drag and drop</a>`!
 WARN: Missing @param tag for parameter `useEmojiAsDefaultStyle` of function androidx\.emoji\.text/EmojiCompat\.Config/setUseEmojiAsDefaultStyle/\#boolean\#java\.util\.List<java\.lang\.Integer>/PointingToDeclaration/
 WARN: Missing @param tag for parameter `useEmojiAsDefaultStyle` of function androidx\.emoji[0-9]+\.text/EmojiCompat\.Config/setUseEmojiAsDefaultStyle/\#boolean\#java\.util\.List<java\.lang\.Integer>/PointingToDeclaration/
-WARN: Missing @param tag for parameter `speed` of function androidx\.media[0-9]+\.common\.util/Util/getPlayoutDurationForMediaDuration/\#long\#float/PointingToDeclaration/
-WARN: Missing @param tag for parameter `length` of function androidx\.media[0-9]+\.datasource/DataSourceUtil/readExactly/\#androidx\.media[0-9]+\.datasource\.DataSource\#int/PointingToDeclaration/
-WARN: Missing @param tag for parameter `allowCrossProtocolRedirects` of function androidx\.media[0-9]+\.datasource/DefaultDataSource/DefaultDataSource/\#android\.content\.Context\#boolean/PointingToDeclaration/
 WARN: Missing @param tag for parameter `inflater` of function androidx\.fragment\.app/Fragment/onCreateOptionsMenu/\#android\.view\.Menu\#android\.view\.MenuInflater/PointingToDeclaration/
 WARN: Missing @param tag for parameter `hardwareBuffer` of function androidx\.graphics\.lowlatency/FrameBuffer/FrameBuffer/\#androidx\.graphics\.opengl\.egl\.EGLSpec\#android\.hardware\.HardwareBuffer/PointingToDeclaration/
 WARN: Missing @param tag for parameter `context` of function androidx\.graphics\.opengl\.egl/EGLSpec/eglMakeCurrent/\#android\.opengl\.EGLContext\#android\.opengl\.EGLSurface\#android\.opengl\.EGLSurface/PointingToDeclaration/
@@ -503,10 +498,6 @@
 WARN: Missing @param tag for parameter `detailsPresenter` of function androidx\.leanback\.widget/FullWidthDetailsOverviewRowPresenter\.ViewHolder/ViewHolder/\#android\.view\.View\#androidx\.leanback\.widget\.Presenter\#androidx\.leanback\.widget\.DetailsOverviewLogoPresenter/PointingToDeclaration/
 WARN: Missing @param tag for parameter `logoPresenter` of function androidx\.leanback\.widget/FullWidthDetailsOverviewRowPresenter\.ViewHolder/ViewHolder/\#android\.view\.View\#androidx\.leanback\.widget\.Presenter\#androidx\.leanback\.widget\.DetailsOverviewLogoPresenter/PointingToDeclaration/
 WARN: Missing @param tag for parameter `parent` of function androidx\.leanback\.widget/GridLayoutManager/requestChildRectangleOnScreen/\#androidx\.recyclerview\.widget\.RecyclerView\#android\.view\.View\#android\.graphics\.Rect\#boolean/PointingToDeclaration/
-WARN: Missing @param tag for parameter `playerEmsgHandler` of function androidx\.media[0-9]+\.exoplayer\.dash/DashChunkSource\.Factory/createDashChunkSource/\#androidx\.media[0-9]+\.exoplayer\.upstream\.LoaderErrorThrower\#androidx\.media[0-9]+\.exoplayer\.dash\.manifest\.DashManifest\#androidx\.media[0-9]+\.exoplayer\.dash\.BaseUrlExclusionList\#int\#int\[\]\#androidx\.media[0-9]+\.exoplayer\.trackselection\.ExoTrackSelection\#int\#long\#boolean\#java\.util\.List<androidx\.media[0-9]+\.common\.Format>\#androidx\.media[0-9]+\.exoplayer\.dash\.PlayerEmsgHandler\.PlayerTrackEmsgHandler\#androidx\.media[0-9]+\.datasource\.TransferListener\#androidx\.media[0-9]+\.exoplayer\.analytics\.PlayerId/PointingToDeclaration/
-WARN: Missing @param tag for parameter `periodIndex` of function androidx\.media[0-9]+\.exoplayer\.dash/DashChunkSource/updateManifest/\#androidx\.media[0-9]+\.exoplayer\.dash\.manifest\.DashManifest\#int/PointingToDeclaration/
-WARN: Missing @param tag for parameter `playerEmsgHandler` of function androidx\.media[0-9]+\.exoplayer\.dash/DefaultDashChunkSource\.Factory/createDashChunkSource/\#androidx\.media[0-9]+\.exoplayer\.upstream\.LoaderErrorThrower\#androidx\.media[0-9]+\.exoplayer\.dash\.manifest\.DashManifest\#androidx\.media[0-9]+\.exoplayer\.dash\.BaseUrlExclusionList\#int\#int\[\]\#androidx\.media[0-9]+\.exoplayer\.trackselection\.ExoTrackSelection\#int\#long\#boolean\#java\.util\.List<androidx\.media[0-9]+\.common\.Format>\#androidx\.media[0-9]+\.exoplayer\.dash\.PlayerEmsgHandler\.PlayerTrackEmsgHandler\#androidx\.media[0-9]+\.datasource\.TransferListener\#androidx\.media[0-9]+\.exoplayer\.analytics\.PlayerId/PointingToDeclaration/
-WARN: Missing @param tag for parameter `newPeriodIndex` of function androidx\.media[0-9]+\.exoplayer\.dash/DefaultDashChunkSource/updateManifest/\#androidx\.media[0-9]+\.exoplayer\.dash\.manifest\.DashManifest\#int/PointingToDeclaration/
 WARN: Missing @param tag for parameter `name` of function androidx\.leanback\.widget/Parallax/createProperty/\#java\.lang\.String\#int/PointingToDeclaration/
 WARN: Missing @param tag for parameter `id` of function androidx\.leanback\.widget/PlaybackControlsRow\.ThumbsAction/ThumbsAction/\#int\#android\.content\.Context\#int\#int/PointingToDeclaration/
 WARN: Missing @param tag for parameter `solidIconIndex` of function androidx\.leanback\.widget/PlaybackControlsRow\.ThumbsAction/ThumbsAction/\#int\#android\.content\.Context\#int\#int/PointingToDeclaration/
@@ -522,50 +513,13 @@
 WARN: Use @androidx\.annotation\.Nullable, not @javax\.annotation/Nullable///PointingToDeclaration/
 WARN: Missing @param tag for parameter `metadata` of function androidx\.media[0-9]+\.player/MediaPlayer/setPlaylist/\#java\.util\.List<androidx\.media[0-9]+\.common\.MediaItem>\#androidx\.media[0-9]+\.common\.MediaMetadata/PointingToDeclaration/
 WARN: Missing @param tag for parameter `controller` of function androidx\.media[0-9]+\.session/MediaSession/sendCustomCommand/\#androidx\.media[0-9]+\.session\.MediaSession\.ControllerInfo\#androidx\.media[0-9]+\.session\.SessionCommand\#android\.os\.Bundle/PointingToDeclaration/
-WARN: Missing @param tag for parameter `runnable` of function androidx\.media[0-9]+\.test\.utils/Action\.ExecuteRunnable/ExecuteRunnable/\#java\.lang\.String\#java\.lang\.Runnable/PointingToDeclaration/
-WARN: Missing @param tag for parameter `drmSessionManager` of function androidx\.media[0-9]+\.test\.utils/FakeAdaptiveMediaSource/createMediaPeriod/\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSource\.MediaPeriodId\#androidx\.media[0-9]+\.exoplayer\.source\.TrackGroupArray\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.datasource\.TransferListener/PointingToDeclaration/
-WARN: Missing @param tag for parameter `params` of function androidx\.media[0-9]+\.test\.utils/FakeTrackSelector/selectAllTracks/\#androidx\.media[0-9]+\.exoplayer\.trackselection\.MappingTrackSelector\.MappedTrackInfo\#int\[\]\[\]\[\]\#int\[\]\#androidx\.media[0-9]+\.exoplayer\.trackselection\.DefaultTrackSelector\.Parameters/PointingToDeclaration/
-WARN: Missing @param tag for parameter `startPositionUs` of function androidx\.media[0-9]+\.test\.utils/MediaSourceTestRunner/createPeriod/\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSource\.MediaPeriodId\#long/PointingToDeclaration/
-WARN: Missing @param tag for parameter `factory` of function androidx\.media[0-9]+\.test\.utils/ExtractorAsserts/assertBehavior/\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.ExtractorFactory\#java\.lang\.String\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.AssertionConfig\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.SimulationConfig/PointingToDeclaration/
-WARN: Missing @param tag for parameter `file` of function androidx\.media[0-9]+\.test\.utils/ExtractorAsserts/assertBehavior/\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.ExtractorFactory\#java\.lang\.String\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.AssertionConfig\#androidx\.media[0-9]+\.test\.utils\.ExtractorAsserts\.SimulationConfig/PointingToDeclaration/
-WARN: Missing @param tag for parameter `drmSessionManager` of function androidx\.media[0-9]+\.test\.utils/FakeMediaSource/createMediaPeriod/\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSource\.MediaPeriodId\#androidx\.media[0-9]+\.exoplayer\.source\.TrackGroupArray\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.datasource\.TransferListener/PointingToDeclaration/
-WARN: Missing @param tag for parameter `manifests` of function androidx\.media[0-9]+\.test\.utils/FakeTimeline/FakeTimeline/\#java\.lang\.Object\[\]\#androidx\.media[0-9]+\.test\.utils\.FakeTimeline\.TimelineWindowDefinition\.\.\./PointingToDeclaration/
-WARN: Missing @param tag for parameter `manifests` of function androidx\.media[0-9]+\.test\.utils/FakeTimeline/FakeTimeline/\#java\.lang\.Object\[\]\#androidx\.media[0-9]+\.exoplayer\.source\.ShuffleOrder\#androidx\.media[0-9]+\.test\.utils\.FakeTimeline\.TimelineWindowDefinition\.\.\./PointingToDeclaration/
-WARN: Missing @param tag for parameter `shuffleOrder` of function androidx\.media[0-9]+\.test\.utils/FakeTimeline/FakeTimeline/\#java\.lang\.Object\[\]\#androidx\.media[0-9]+\.exoplayer\.source\.ShuffleOrder\#androidx\.media[0-9]+\.test\.utils\.FakeTimeline\.TimelineWindowDefinition\.\.\./PointingToDeclaration/
-WARN: Missing @param tag for parameter `allocator` of function androidx\.media[0-9]+\.test\.utils/FakeMediaPeriod/createSampleStream/\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.common\.Format\#java\.util\.List<androidx\.media[0-9]+\.test\.utils\.FakeSampleStream\.FakeSampleStreamItem>/PointingToDeclaration/
-WARN: Missing @param tag for parameter `timeline` of function androidx\.media[0-9]+\.test\.utils/TimelineAsserts/assertWindowTags/\#androidx\.media[0-9]+\.common\.Timeline\#java\.lang\.Object\.\.\./PointingToDeclaration/
-WARN: Missing @param tag for parameter `target` of function androidx\.media[0-9]+\.test\.utils/ActionSchedule\.Builder/sendMessage/\#androidx\.media[0-9]+\.exoplayer\.PlayerMessage\.Target\#long/PointingToDeclaration/
-WARN: Missing @param tag for parameter `sources` of function androidx\.media[0-9]+\.test\.utils/ActionSchedule\.Builder/setMediaSources/\#boolean\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSource\.\.\./PointingToDeclaration/
-WARN: Missing @param tag for parameter `sources` of function androidx\.media[0-9]+\.test\.utils/ActionSchedule\.Builder/setMediaSources/\#int\#long\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSource\.\.\./PointingToDeclaration/
 WARN: Failed to resolve `@see <a href="http://developer\.android\.com/guide/topics/ui/controls/pickers\.html">Pickers API`!
 WARN: Failed to resolve `@see <a href="http://developer\.android\.com/design/patterns/navigation\-drawer\.html">Navigation`!
 WARN: Missing @param tag for parameter `verificationMode` of function androidx\.test\.espresso\.intent/Intents/intended/\#org\.hamcrest\.Matcher<android\.content\.Intent>\#androidx\.test\.espresso\.intent\.VerificationMode/PointingToDeclaration/
 WARNING: link to @throws type AssertionFailedError does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name,  e\.g\.`@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root=CustomDocTag\(children=\[P\(children=\[Text\(body=if the given , children=\[\], params=\{\}\), DocumentationLink\(dri=org\.hamcrest/Matcher///PointingToDeclaration/, children=\[Text\(body=Matcher, children=\[\], params=\{\}\)\], params=\{\}\), Text\(body= did not match the expected number of recorded intents, children=\[\], params=\{\}\)\], params=\{\}\)\], params=\{\}, name=MARKDOWN_FILE\), name=AssertionFailedError, exceptionAddress=null\)\.`
 WARNING: link to @throws type AssertionFailedError does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name,  e\.g\.`@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root=CustomDocTag\(children=\[P\(children=\[Text\(body=if the given , children=\[\], params=\{\}\), DocumentationLink\(dri=org\.hamcrest/Matcher///PointingToDeclaration/, children=\[Text\(body=Matcher, children=\[\], params=\{\}\)\], params=\{\}\), Text\(body= did not match any or matched more than one of the recorded intents, children=\[\], params=\{\}\)\], params=\{\}\)\], params=\{\}, name=MARKDOWN_FILE\), name=AssertionFailedError, exceptionAddress=null\)\.`
 WARN: Missing @param tag for parameter `extras` of function androidx\.media[0-9]+\.session/MediaController/setMediaUri/\#android\.net\.Uri\#android\.os\.Bundle/PointingToDeclaration/
-WARN: Missing @param tag for parameter `trackType` of function androidx\.media[0-9]+\.common/Tracks/isTypeSupported/\#int\#boolean/PointingToDeclaration/
-WARN: Missing @param tag for parameter `adIndexInAdGroup` of function androidx\.media[0-9]+\.common/Timeline\.Period/getAdState/\#int\#int/PointingToDeclaration/
-WARN: Missing @param tag for parameter `dataSourceFactory` of function androidx\.media[0-9]+\.exoplayer\.drm/OfflineLicenseHelper/newWidevineInstance/\#java\.lang\.String\#boolean\#androidx\.media[0-9]+\.datasource\.DataSource\.Factory\#java\.util\.Map<java\.lang\.String,java\.lang\.String>\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher/PointingToDeclaration/
-WARN: Missing @param tag for parameter `drmEventDispatcher` of function androidx\.media[0-9]+\.exoplayer\.hls/HlsMediaPeriod/HlsMediaPeriod/\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsExtractorFactory\#androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsPlaylistTracker\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsDataSourceFactory\#androidx\.media[0-9]+\.datasource\.TransferListener\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.LoadErrorHandlingPolicy\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.CompositeSequenceableLoaderFactory\#boolean\#int\#boolean\#androidx\.media[0-9]+\.exoplayer\.analytics\.PlayerId/PointingToDeclaration/
-WARN: Missing @param tag for parameter `metadataType` of function androidx\.media[0-9]+\.exoplayer\.hls/HlsMediaPeriod/HlsMediaPeriod/\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsExtractorFactory\#androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsPlaylistTracker\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsDataSourceFactory\#androidx\.media[0-9]+\.datasource\.TransferListener\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.LoadErrorHandlingPolicy\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.CompositeSequenceableLoaderFactory\#boolean\#int\#boolean\#androidx\.media[0-9]+\.exoplayer\.analytics\.PlayerId/PointingToDeclaration/
-WARN: Missing @param tag for parameter `playerId` of function androidx\.media[0-9]+\.exoplayer\.hls/HlsMediaPeriod/HlsMediaPeriod/\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsExtractorFactory\#androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsPlaylistTracker\#androidx\.media[0-9]+\.exoplayer\.hls\.HlsDataSourceFactory\#androidx\.media[0-9]+\.datasource\.TransferListener\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionManager\#androidx\.media[0-9]+\.exoplayer\.drm\.DrmSessionEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.LoadErrorHandlingPolicy\#androidx\.media[0-9]+\.exoplayer\.source\.MediaSourceEventListener\.EventDispatcher\#androidx\.media[0-9]+\.exoplayer\.upstream\.Allocator\#androidx\.media[0-9]+\.exoplayer\.source\.CompositeSequenceableLoaderFactory\#boolean\#int\#boolean\#androidx\.media[0-9]+\.exoplayer\.analytics\.PlayerId/PointingToDeclaration/
-WARN: Missing @param tag for parameter `preciseStart` of function androidx\.media[0-9]+\.exoplayer\.hls\.playlist/HlsMediaPlaylist/HlsMediaPlaylist/\#int\#java\.lang\.String\#java\.util\.List<java\.lang\.String>\#long\#boolean\#long\#boolean\#int\#long\#int\#long\#long\#boolean\#boolean\#boolean\#androidx\.media[0-9]+\.common\.DrmInitData\#java\.util\.List<androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.Segment>\#java\.util\.List<androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.Part>\#androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.ServerControl\#java\.util\.Map<android\.net\.Uri,androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.RenditionReport>/PointingToDeclaration/
-WARN: Missing @param tag for parameter `partTargetDurationUs` of function androidx\.media[0-9]+\.exoplayer\.hls\.playlist/HlsMediaPlaylist/HlsMediaPlaylist/\#int\#java\.lang\.String\#java\.util\.List<java\.lang\.String>\#long\#boolean\#long\#boolean\#int\#long\#int\#long\#long\#boolean\#boolean\#boolean\#androidx\.media[0-9]+\.common\.DrmInitData\#java\.util\.List<androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.Segment>\#java\.util\.List<androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.Part>\#androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.ServerControl\#java\.util\.Map<android\.net\.Uri,androidx\.media[0-9]+\.exoplayer\.hls\.playlist\.HlsMediaPlaylist\.RenditionReport>/PointingToDeclaration/
-WARN: Missing @param tag for parameter `codecAdapterFactory` of function androidx\.media[0-9]+\.exoplayer\.mediacodec/MediaCodecRenderer/MediaCodecRenderer/\#int\#androidx\.media[0-9]+\.exoplayer\.mediacodec\.MediaCodecAdapter\.Factory\#androidx\.media[0-9]+\.exoplayer\.mediacodec\.MediaCodecSelector\#boolean\#float/PointingToDeclaration/
-WARN: Missing @param tag for parameter `dataSource` of function androidx\.media[0-9]+\.exoplayer\.offline/SegmentDownloader/getManifest/\#androidx\.media[0-9]+\.datasource\.DataSource\#androidx\.media[0-9]+\.datasource\.DataSpec\#boolean/PointingToDeclaration/
 WARN: Use @androidx\.annotation\.Nullable, not @org\.checkerframework\.checker\.nullness\.qual/Nullable///PointingToDeclaration/
-WARN: Failed to resolve `@see <a href="http://msdn\.microsoft\.com/en\-us/library/ee[0-9]+\(v=vs\.[0-9]+\)\.aspx">IIS Smooth`!
-WARN: Missing @param tag for parameter `type` of function androidx\.media[0-9]+\.exoplayer\.trackselection/RandomTrackSelection/RandomTrackSelection/\#androidx\.media[0-9]+\.common\.TrackGroup\#int\[\]\#int\#java\.util\.Random/PointingToDeclaration/
-WARN: Missing @param tag for parameter `trackIndices` of function androidx\.media[0-9]+\.exoplayer\.trackselection/MappingTrackSelector\.MappedTrackInfo/getAdaptiveSupport/\#int\#int\#int\[\]/PointingToDeclaration/
-WARN: Missing @param tag for parameter `params` of function androidx\.media[0-9]+\.exoplayer\.trackselection/DefaultTrackSelector/selectAllTracks/\#androidx\.media[0-9]+\.exoplayer\.trackselection\.MappingTrackSelector\.MappedTrackInfo\#int\[\]\[\]\[\]\#int\[\]\#androidx\.media[0-9]+\.exoplayer\.trackselection\.DefaultTrackSelector\.Parameters/PointingToDeclaration/
-WARN: Failed to resolve `@see <a href="http://en\.wikipedia\.org/wiki/Moving_average">Wiki: Moving average</a>`!
-WARN: Failed to resolve `@see <a href="http://en\.wikipedia\.org/wiki/Selection_algorithm">Wiki: Selection algorithm</a>`!
-WARN: Missing @param tag for parameter `decoderName` of function androidx\.media[0-9]+\.exoplayer\.video/DecoderVideoRenderer/canReuseDecoder/\#java\.lang\.String\#androidx\.media[0-9]+\.common\.Format\#androidx\.media[0-9]+\.common\.Format/PointingToDeclaration/
-WARN: Missing @param tag for parameter `flacStreamMetadata` of function androidx\.media[0-9]+\.extractor/FlacFrameReader/getFirstSampleNumber/\#androidx\.media[0-9]+\.extractor\.ExtractorInput\#androidx\.media[0-9]+\.extractor\.FlacStreamMetadata/PointingToDeclaration/
-WARN: Failed to resolve `@see <a href="http://www\.w[0-9]+\.org/TR/ttaf[0-9]+\-dfxp/">TTML specification</a>`!
-WARN: Failed to resolve `@see <a href="http://dev\.w[0-9]+\.org/html[0-9]+/webvtt">WebVTT specification</a>`!
-WARN: Failed to resolve `@see <a href="https://www\.w[0-9]+\.org/TR/CSS[0-9]+/cascade\.html">CSS Cascading</a>`!
-Did you mean <a href="https://www\.w[0-9]+\.org/TR/CSS[0-9]+/cascade\#html">CSS Cascading</a>\?
 WARN: Failed to resolve `@see <a href="https://developer\.android\.com/guide/topics/media/media\-routing">Media Routing</a>`!
 WARN: Missing @param tag for parameter `context` of function androidx\.mediarouter\.media/RemotePlaybackClient/RemotePlaybackClient/\#android\.content\.Context\#androidx\.mediarouter\.media\.MediaRouter\.RouteInfo/PointingToDeclaration/
 WARN: Failed to resolve `@see NavAction\.getDefaultArguments`!
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index d9480fe..348026d 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -169,8 +169,17 @@
 
         assertEnterPopExit(fragment)
 
+        val view = fragment.requireView()
+        val layoutCountDownLatch = CountDownLatch(1)
+        // We need to wait for the View to change its visibility before asserting
+        view.viewTreeObserver.addOnGlobalLayoutListener {
+            layoutCountDownLatch.countDown()
+        }
+
+        assertThat(layoutCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
         activityRule.runOnUiThread {
-            assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+            assertThat(view.visibility).isEqualTo(View.GONE)
         }
     }
 
@@ -207,8 +216,16 @@
 
         assertEnterPopExit(fragment)
 
+        val view = fragment.requireView()
+        val layoutCountDownLatch = CountDownLatch(1)
+        // We need to wait for the View to change its visibility before asserting
+        view.viewTreeObserver.addOnGlobalLayoutListener {
+            layoutCountDownLatch.countDown()
+        }
+
+        assertThat(layoutCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
         activityRule.runOnUiThread {
-            assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+            assertThat(view.visibility).isEqualTo(View.GONE)
         }
     }
 
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
index 6d79fe1..21f3854 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
@@ -180,7 +181,7 @@
      */
     static class AnimationOrAnimator {
         public final Animation animation;
-        public final Animator animator;
+        public final AnimatorSet animator;
 
         AnimationOrAnimator(Animation animation) {
             this.animation = animation;
@@ -192,7 +193,8 @@
 
         AnimationOrAnimator(Animator animator) {
             this.animation = null;
-            this.animator = animator;
+            this.animator = new AnimatorSet();
+            this.animator.play(animator);
             if (animator == null) {
                 throw new IllegalStateException("Animator cannot be null");
             }
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
index 0e11321..9107a59 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
@@ -139,6 +139,19 @@
         </receiver>
 
         <receiver
+            android:name="androidx.glance.appwidget.demos.TypographyDemoAppWidgetReceiver"
+            android:label="@string/typography_widget_name"
+            android:enabled="@bool/glance_appwidget_available"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/default_app_widget_info" />
+        </receiver>
+
+        <receiver
             android:name="androidx.glance.appwidget.demos.ScrollableAppWidgetReceiver"
             android:label="@string/scrollable_widget_name"
             android:enabled="@bool/glance_appwidget_available"
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/TypogryaphDemoAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/TypogryaphDemoAppWidget.kt
new file mode 100644
index 0000000..dee09c1
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/TypogryaphDemoAppWidget.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.appwidget.demos
+
+import android.content.Context
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.provideContent
+import androidx.glance.background
+import androidx.glance.layout.Column
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.fillMaxWidth
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+import androidx.glance.layout.wrapContentHeight
+import androidx.glance.text.FontFamily
+import androidx.glance.text.FontWeight
+import androidx.glance.text.Text
+import androidx.glance.text.TextAlign
+import androidx.glance.text.TextStyle
+
+/**
+ * Sample AppWidget showcases Glance text styles.
+ */
+class TypographyDemoAppWidget : GlanceAppWidget() {
+    override val sizeMode: SizeMode = SizeMode.Exact
+
+    override suspend fun provideGlance(
+        context: Context,
+        id: GlanceId
+    ) = provideContent {
+        Column(
+            modifier = GlanceModifier.fillMaxSize()
+                .background(GlanceTheme.colors.background).padding(8.dp)
+        ) {
+            Column {
+                Text(
+                    "Text Component Demo Widget",
+                    modifier = GlanceModifier.fillMaxWidth().wrapContentHeight(),
+                    style = TextStyle(
+                        fontWeight = FontWeight.Bold,
+                        fontSize = 24.sp,
+                        textAlign = TextAlign.Center
+                    )
+                )
+                Spacer(GlanceModifier.size(16.dp))
+                Text(
+                    "Glance text styles:",
+                    style = TextStyle(fontSize = 18.sp)
+                )
+                Spacer(GlanceModifier.size(20.dp))
+            }
+
+            LazyColumn {
+                item {
+                    Text(
+                        "Display Large",
+                        style = TextStyle(
+                            fontSize = 57.sp,
+                            fontWeight = FontWeight.Normal,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Headline Large",
+                        style = TextStyle(
+                            fontSize = 32.sp,
+                            fontWeight = FontWeight.Normal,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Headline Small",
+                        style = TextStyle(
+                            fontSize = 24.sp,
+                            fontWeight = FontWeight.Normal,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Title Medium",
+                        style = TextStyle(
+                            fontSize = 16.sp,
+                            fontWeight = FontWeight.Medium,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Body Medium",
+                        style = TextStyle(
+                            fontSize = 14.sp,
+                            fontWeight = FontWeight.Normal,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Label Large",
+                        style = TextStyle(
+                            fontSize = 14.sp,
+                            fontWeight = FontWeight.Medium,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+                item {
+                    Text(
+                        "Label Medium",
+                        style = TextStyle(
+                            fontSize = 12.sp,
+                            fontWeight = FontWeight.Medium,
+                            fontFamily = FontFamily.SansSerif
+                        )
+                    )
+                }
+            }
+        }
+    }
+}
+
+class TypographyDemoAppWidgetReceiver : GlanceAppWidgetReceiver() {
+    override val glanceAppWidget: GlanceAppWidget = TypographyDemoAppWidget()
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
index 638118d..e74682e 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
@@ -28,6 +28,7 @@
     <string name="compound_button_widget_name">Compound buttons Widget</string>
     <string name="error_widget_name">Error UI Widget</string>
     <string name="font_widget_name">Font Demo Widget</string>
+    <string name="typography_widget_name">Typography Demo Widget</string>
     <string name="scrollable_widget_name">Scrollable Widget</string>
     <string name="image_widget_name">Image Widget</string>
     <string name="ripple_widget_name">Ripple Widget</string>
diff --git a/lifecycle/lifecycle-common/api/restricted_current.txt b/lifecycle/lifecycle-common/api/restricted_current.txt
index 0f03c8d..be2c2c0 100644
--- a/lifecycle/lifecycle-common/api/restricted_current.txt
+++ b/lifecycle/lifecycle-common/api/restricted_current.txt
@@ -94,6 +94,7 @@
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class Lifecycling {
     method public static String getAdapterName(String className);
     method public static androidx.lifecycle.LifecycleEventObserver lifecycleEventObserver(Object object);
+    field public static final androidx.lifecycle.Lifecycling INSTANCE;
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MethodCallsLogger {
diff --git a/lifecycle/lifecycle-common/lint-baseline.xml b/lifecycle/lifecycle-common/lint-baseline.xml
deleted file mode 100644
index f182855..0000000
--- a/lifecycle/lifecycle-common/lint-baseline.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.1.0-alpha07">
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="public interface GeneratedAdapter {"
-        errorLine2="                 ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/GeneratedAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="public interface GenericLifecycleObserver extends LifecycleEventObserver {"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/GenericLifecycleObserver.java"/>
-    </issue>
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="    public var internalScopeRef: AtomicReference&lt;Any> = AtomicReference&lt;Any>()"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/Lifecycle.kt"/>
-    </issue>
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="public object Lifecycling {"
-        errorLine2="              ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/Lifecycling.kt"/>
-    </issue>
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="public open class MethodCallsLogger() {"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/MethodCallsLogger.kt"/>
-    </issue>
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="    public open fun approveCall(name: String, type: Int): Boolean {"
-        errorLine2="                    ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/MethodCallsLogger.kt"/>
-    </issue>
-
-</issues>
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GeneratedAdapter.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GeneratedAdapter.kt
index ce776e5..e384aae 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GeneratedAdapter.kt
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GeneratedAdapter.kt
@@ -17,9 +17,6 @@
 
 import androidx.annotation.RestrictTo
 
-/**
- * @hide
- */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 public interface GeneratedAdapter {
     /**
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java
index 1c0c70a..9a7acb4 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java
@@ -20,7 +20,6 @@
 
 /**
  * Class that can receive any lifecycle change and dispatch it to the receiver.
- * @hide
  *
  * @deprecated and it is scheduled to be removed in lifecycle 3.0
  */
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt
index f69c624..bbf1313 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt
@@ -56,9 +56,11 @@
     /**
      * Lifecycle coroutines extensions stashes the CoroutineScope into this field.
      *
-     * @hide used by lifecycle-common-ktx
+     * RestrictTo as it is used by lifecycle-common-ktx
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public var internalScopeRef: AtomicReference<Any> = AtomicReference<Any>()
 
     /**
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycling.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycling.kt
index f3a1b9f..64ce3be 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycling.kt
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycling.kt
@@ -21,8 +21,6 @@
 
 /**
  * Internal class to handle lifecycle conversion etc.
- *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 public object Lifecycling {
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/MethodCallsLogger.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/MethodCallsLogger.kt
index c14c15e..4e8b9d3 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/MethodCallsLogger.kt
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/MethodCallsLogger.kt
@@ -17,16 +17,10 @@
 
 import androidx.annotation.RestrictTo
 
-/**
- * @hide
- */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public open class MethodCallsLogger() {
+public open class MethodCallsLogger {
     private val calledMethods: MutableMap<String, Int> = HashMap()
 
-    /**
-     * @hide
-     */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public open fun approveCall(name: String, type: Int): Boolean {
         val nullableMask = calledMethods[name]
diff --git a/lifecycle/lifecycle-runtime/lint-baseline.xml b/lifecycle/lifecycle-runtime/lint-baseline.xml
deleted file mode 100644
index c346106..0000000
--- a/lifecycle/lifecycle-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.1.0-alpha07">
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="open class ReportFragment() : android.app.Fragment() {"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/ReportFragment.kt"/>
-    </issue>
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/ReportFragment.kt b/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/ReportFragment.kt
index a7ae3f40..9873795 100644
--- a/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/ReportFragment.kt
+++ b/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/ReportFragment.kt
@@ -24,8 +24,6 @@
 
 /**
  * Internal class that dispatches initialization events.
- *
- * @hide
  */
 @Suppress("DEPRECATION")
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/lint-baseline.xml b/lifecycle/lifecycle-viewmodel-savedstate/lint-baseline.xml
deleted file mode 100644
index 5035758..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.1.0-alpha07">
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="    override fun onRequery(viewModel: ViewModel) {"
-        errorLine2="                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt"/>
-    </issue>
-
-</issues>
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
index 3e09793..2e06e75 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
@@ -141,9 +141,6 @@
         handle: SavedStateHandle
     ): T
 
-    /**
-     * @hide
-     */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     override fun onRequery(viewModel: ViewModel) {
         // is need only for legacy path
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 1b0b02e..6caf6d9 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -29,10 +29,10 @@
     implementation(libs.kotlinStdlib)
     implementation(projectOrArtifact(":compose:foundation:foundation-layout"))
     api("androidx.activity:activity-compose:1.7.0")
-    api(projectOrArtifact(":compose:animation:animation"))
-    api(projectOrArtifact(":compose:runtime:runtime"))
-    api(projectOrArtifact(":compose:runtime:runtime-saveable"))
-    api(projectOrArtifact(":compose:ui:ui"))
+    api("androidx.compose.animation:animation:1.5.0-beta01")
+    api("androidx.compose.runtime:runtime:1.5.0-beta01")
+    api("androidx.compose.runtime:runtime-saveable:1.5.0-beta01")
+    api("androidx.compose.ui:ui:1.5.0-beta01")
     api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
 
diff --git a/privacysandbox/ads/ads-adservices/src/main/AndroidManifest.xml b/privacysandbox/ads/ads-adservices/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..272870f
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <uses-library android:name="android.ext.adservices" android:required="false"/>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/BaseTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/BaseTest.java
index da92d0f..fac1c20 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/BaseTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/BaseTest.java
@@ -22,9 +22,11 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -46,7 +48,7 @@
 @RunWith(AndroidJUnit4.class)
 public abstract class BaseTest {
 
-    private static final long TIMEOUT_MS = 10_000;
+    protected static final long TIMEOUT_MS = 10_000;
     protected static final String TEST_APP = "androidx.test.uiautomator.testapp";
     protected static final int DEFAULT_FLAGS =
             Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
@@ -77,8 +79,13 @@
     }
 
     protected void launchTestActivity(@NonNull Class<? extends Activity> activity) {
+        launchTestActivity(activity, new Intent().setFlags(DEFAULT_FLAGS), null);
+    }
+
+    protected void launchTestActivity(@NonNull Class<? extends Activity> activity,
+            @NonNull Intent intent, @Nullable Bundle options) {
         Context context = ApplicationProvider.getApplicationContext();
-        context.startActivity(new Intent().setFlags(DEFAULT_FLAGS).setClass(context, activity));
+        context.startActivity(new Intent(intent).setClass(context, activity), options);
         assertTrue("Test app not visible after launching activity",
                 mDevice.wait(Until.hasObject(By.pkg(TEST_APP)), TIMEOUT_MS));
     }
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java
new file mode 100644
index 0000000..484dd9f
--- /dev/null
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.test.uiautomator.testapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@SdkSuppress(minSdkVersion = 30)
+public class MultiDisplayTest extends BaseTest {
+    private static final int MULTI_DISPLAY_FLAGS =
+            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+
+    @Before
+    public void assumeMultiDisplay() {
+        // Tests need to run with multiple displays.
+        assumeTrue(getDisplayIds().size() > 1);
+    }
+
+    @Test
+    public void testMultiDisplay_click() {
+        int selectedDisplayId = getSecondaryDisplayId();
+        launchTestActivityOnDisplay(ClickTestActivity.class, selectedDisplayId);
+
+        UiObject2 button = mDevice.findObject(By.res(TEST_APP, "button1"));
+        button.click();
+        assertEquals(selectedDisplayId, button.getDisplayId());
+        assertTrue(button.wait(Until.textEquals("text1_clicked"), TIMEOUT_MS));
+    }
+
+    // Helper to launch an activity on a specific display.
+    private void launchTestActivityOnDisplay(@NonNull Class<? extends Activity> activity,
+            int displayId) {
+        launchTestActivity(activity, new Intent().setFlags(MULTI_DISPLAY_FLAGS),
+                ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle());
+    }
+
+    // Helper to get all the display IDs in the current testing environment.
+    private static Set<Integer> getDisplayIds() {
+        Context context = ApplicationProvider.getApplicationContext();
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        return Arrays.stream(displayManager.getDisplays()).map(Display::getDisplayId).collect(
+                Collectors.toSet());
+    }
+
+    // Helper to get the ID of the first non-default display.
+    private static int getSecondaryDisplayId() {
+        int selectedDisplayId = 0;
+        for (int displayId : getDisplayIds()) {
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                selectedDisplayId = displayId;
+                break;
+            }
+        }
+        return selectedDisplayId;
+    }
+}
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
index 8f82b96..6245f3c 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
@@ -26,6 +26,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.view.Display;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
@@ -341,6 +342,14 @@
     }
 
     @Test
+    public void testGetDisplayId() {
+        launchTestActivity(MainActivity.class);
+
+        UiObject2 button = mDevice.findObject(By.res(TEST_APP, "button"));
+        assertEquals(Display.DEFAULT_DISPLAY, button.getDisplayId());
+    }
+
+    @Test
     public void testGetVisibleBounds() {
         launchTestActivity(VisibleBoundsTestActivity.class);
 
diff --git a/tracing/tracing-ktx/src/androidTest/java/androidx/tracing/TraceTestKt.kt b/tracing/tracing-ktx/src/androidTest/java/androidx/tracing/TraceTestKt.kt
index f192bb3..850d56a 100644
--- a/tracing/tracing-ktx/src/androidTest/java/androidx/tracing/TraceTestKt.kt
+++ b/tracing/tracing-ktx/src/androidTest/java/androidx/tracing/TraceTestKt.kt
@@ -37,6 +37,13 @@
     }
 
     @Test
+    fun testNoCrossInline() {
+        trace("Test") {
+            return
+        }
+    }
+
+    @Test
     fun traceLazyTest() {
         assertFalse(
             "This test expects to be run without tracing enabled in this process",
diff --git a/tracing/tracing-ktx/src/main/java/androidx/tracing/Trace.kt b/tracing/tracing-ktx/src/main/java/androidx/tracing/Trace.kt
index 27e9196..1affd9e 100644
--- a/tracing/tracing-ktx/src/main/java/androidx/tracing/Trace.kt
+++ b/tracing/tracing-ktx/src/main/java/androidx/tracing/Trace.kt
@@ -23,7 +23,7 @@
  * @param label A name of the code section to appear in the trace.
  * @param block A block of code which is being traced.
  */
-public inline fun <T> trace(label: String, crossinline block: () -> T): T {
+public inline fun <T> trace(label: String, block: () -> T): T {
     Trace.beginSection(label)
     try {
         return block()
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/CurvedTextTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/CurvedTextTest.kt
new file mode 100644
index 0000000..83d68ee
--- /dev/null
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/CurvedTextTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.curvedRow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RequiresApi(Build.VERSION_CODES.O)
+class CurvedTextTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val testText = "TestText"
+
+    @Test
+    fun color_parameter_overrides_styleColor() {
+        rule.setContent {
+            CurvedLayout {
+                curvedRow {
+                    curvedText(
+                        text = testText,
+                        color = Color.Red,
+                        style = CurvedTextStyle(
+                            color = Color.Blue
+                        )
+                    )
+                }
+            }
+        }
+
+        val curvedTextImage = rule.onNodeWithContentDescription(testText).captureToImage()
+        curvedTextImage.assertContainsColor(Color.Red)
+        curvedTextImage.assertDoesNotContainColor(Color.Blue)
+    }
+
+    @Test
+    fun styleColor_overrides_LocalContentColor() {
+        rule.setContent {
+            CompositionLocalProvider(LocalContentColor provides Color.Yellow) {
+                CurvedLayout {
+                    curvedRow {
+                        curvedText(
+                            text = testText,
+                            style = CurvedTextStyle(
+                                color = Color.Blue
+                            )
+                        )
+                    }
+                }
+            }
+        }
+
+        val curvedTextImage = rule.onNodeWithContentDescription(testText).captureToImage()
+        curvedTextImage.assertContainsColor(Color.Blue)
+        curvedTextImage.assertDoesNotContainColor(Color.Yellow)
+    }
+
+    @Test
+    fun uses_LocalContentColor_as_fallback() {
+        rule.setContent {
+            CompositionLocalProvider(LocalContentColor provides Color.Yellow) {
+                CurvedLayout {
+                    curvedRow {
+                        curvedText(
+                            text = testText,
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithContentDescription(testText).captureToImage()
+            .assertContainsColor(Color.Yellow)
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
index e9644a6..ae16c7b 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.compose.material
 
+import android.os.Build
 import android.text.format.DateFormat
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
@@ -32,8 +33,10 @@
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.unit.sp
+import androidx.test.filters.SdkSuppress
 import androidx.wear.compose.foundation.curvedComposable
 import java.util.Calendar
+import java.util.Locale
 import java.util.TimeZone
 import org.junit.Assert.assertEquals
 import org.junit.Rule
@@ -503,7 +506,8 @@
         rule.setContent {
             MaterialTheme(
                 typography = MaterialTheme.typography.copy(
-                    caption1 = testTextStyle)
+                    caption1 = testTextStyle
+                )
             ) {
                 ConfiguredShapeScreen(false) {
                     TimeText(
@@ -558,6 +562,23 @@
         }
         assertEquals(expectedTime, actualTime)
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    fun formats_current_time_12H_french_locale() {
+        val currentTimeInMillis = 1631544258000L // 2021-09-13 14:44:18
+        val expectedTime = "2 h 44"
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
+
+        var actualTime: String? = null
+        Locale.setDefault(Locale.CANADA_FRENCH)
+
+        rule.setContentWithTheme {
+            val format = TimeTextDefaults.timeFormat()
+            actualTime = currentTime({ currentTimeInMillis }, format).value
+        }
+        assertEquals(expectedTime, actualTime)
+    }
 }
 
 private const val LINEAR_ITEM_TAG = "LINEAR_ITEM_TAG"
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
index 4834be9..5dea0bbb 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
@@ -57,6 +57,7 @@
 import androidx.wear.compose.material.TimeTextDefaults.TextSeparator
 import androidx.wear.compose.material.TimeTextDefaults.timeFormat
 import java.util.Calendar
+import java.util.Locale
 
 /**
  * Layout to show the current time and a label at the top of the screen.
@@ -186,7 +187,11 @@
      * 12h or 24h format
      */
     @Composable
-    public fun timeFormat(): String = if (is24HourFormat()) TimeFormat24Hours else TimeFormat12Hours
+    public fun timeFormat(): String {
+        val format = if (is24HourFormat()) TimeFormat24Hours else TimeFormat12Hours
+        return DateFormat.getBestDateTimePattern(Locale.getDefault(), format)
+            .replace("a", "").trim()
+    }
 
     /**
      * Creates a [TextStyle] with default parameters used for showing time
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 0dd7042..2142a1b 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -141,6 +141,10 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class CurvedTextKt {
+    method public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional long background, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
+  }
+
   @kotlin.RequiresOptIn(message="This Wear Material3 API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearMaterial3Api {
   }
 
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 0dd7042..2142a1b 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -141,6 +141,10 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class CurvedTextKt {
+    method public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional long background, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
+  }
+
   @kotlin.RequiresOptIn(message="This Wear Material3 API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearMaterial3Api {
   }
 
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt
new file mode 100644
index 0000000..2a7c118
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.curvedRow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RequiresApi(Build.VERSION_CODES.O)
+class CurvedTextTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val testText = "TestText"
+
+    @Test
+    fun color_parameter_overrides_styleColor() {
+        rule.setContent {
+            CurvedLayout {
+                curvedRow {
+                    curvedText(
+                        text = testText,
+                        color = Color.Red,
+                        style = CurvedTextStyle(
+                            color = Color.Blue
+                        )
+                    )
+                }
+            }
+        }
+
+        val curvedTextImage = rule.onNodeWithContentDescription(testText).captureToImage()
+        curvedTextImage.assertContainsColor(Color.Red)
+        curvedTextImage.assertDoesNotContainColor(Color.Blue)
+    }
+
+    @Test
+    fun styleColor_overrides_LocalContentColor() {
+        rule.setContent {
+            CompositionLocalProvider(LocalContentColor provides Color.Yellow) {
+                CurvedLayout {
+                    curvedRow {
+                        curvedText(
+                            text = testText,
+                            style = CurvedTextStyle(
+                                color = Color.Blue
+                            )
+                        )
+                    }
+                }
+            }
+        }
+
+        val curvedTextImage = rule.onNodeWithContentDescription(testText).captureToImage()
+        curvedTextImage.assertContainsColor(Color.Blue)
+        curvedTextImage.assertDoesNotContainColor(Color.Yellow)
+    }
+
+    @Test
+    fun uses_LocalContentColor_as_fallback() {
+        rule.setContent {
+            CompositionLocalProvider(LocalContentColor provides Color.Yellow) {
+                CurvedLayout {
+                    curvedRow {
+                        curvedText(
+                            text = testText,
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithContentDescription(testText).captureToImage()
+            .assertContainsColor(Color.Yellow)
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt
new file mode 100644
index 0000000..3b8f9d0
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontSynthesis
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.TextUnit
+import androidx.wear.compose.foundation.CurvedDirection
+import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedModifier
+import androidx.wear.compose.foundation.CurvedScope
+import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.basicCurvedText
+import androidx.wear.compose.foundation.curvedRow
+
+/**
+ * CurvedText is a component allowing developers to easily write curved text following
+ * the curvature a circle (usually at the edge of a circular screen).
+ * CurvedText can be only created within the CurvedLayout to ensure the best experience, like being
+ * able to specify to positioning.
+ *
+ * The default [style] uses the [LocalTextStyle] provided by the [MaterialTheme] / components,
+ * converting it to a [CurvedTextStyle]. Note that not all parameters are used by [curvedText].
+ *
+ * If you are setting your own style, you may want to consider first retrieving [LocalTextStyle],
+ * and using [TextStyle.copy] to keep any theme defined attributes, only modifying the specific
+ * attributes you want to override, then convert to [CurvedTextStyle]
+ *
+ * For ease of use, commonly used parameters from [CurvedTextStyle] are also present here. The
+ * order of precedence is as follows:
+ * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]),
+ * then this parameter will always be used.
+ * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value
+ * from [style] will be used instead.
+ *
+ * Additionally, for [color], if [color] is not set, and [style] does not have a color, then
+ * [LocalContentColor] will be used with an alpha of [LocalContentAlpha]- this allows this
+ * [curvedText] or element containing this [curvedText] to adapt to different background colors and
+ * still maintain contrast and accessibility.
+ *
+ * For samples explicitly specifying style see:
+ * @sample androidx.wear.compose.material.samples.CurvedTextDemo
+ *
+ * For examples using CompositionLocal to specify the style, see:
+ * @sample androidx.wear.compose.material.samples.CurvedTextProviderDemo
+ *
+ * For more information, see the
+ * [Curved Text](https://developer.android.com/training/wearables/compose/curved-text)
+ * guide.
+ *
+ * @param text The text to display
+ * @param modifier The [CurvedModifier] to apply to this curved text.
+ * @param background The background color for the text.
+ * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
+ * this will be [LocalContentColor].
+ * @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize].
+ * @param fontFamily The font family to be used when rendering the text.
+ * @param fontWeight The thickness of the glyphs, in a range of [1, 1000]. see [FontWeight]
+ * @param fontStyle The typeface variant to use when drawing the letters (e.g. italic).
+ * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
+ * or style cannot be found in the provided font family.
+ * @param style Specifies the style to use.
+ * @param angularDirection Specify if the text is laid out clockwise or anti-clockwise, and if
+ * those needs to be reversed in a Rtl layout.
+ * If not specified, it will be inherited from the enclosing [curvedRow] or [CurvedLayout]
+ * See [CurvedDirection.Angular].
+ * @param overflow How visual overflow should be handled.
+ */
+public fun CurvedScope.curvedText(
+    text: String,
+    modifier: CurvedModifier = CurvedModifier,
+    background: Color = Color.Unspecified,
+    color: Color = Color.Unspecified,
+    fontSize: TextUnit = TextUnit.Unspecified,
+    fontFamily: FontFamily? = null,
+    fontWeight: FontWeight? = null,
+    fontStyle: FontStyle? = null,
+    fontSynthesis: FontSynthesis? = null,
+    style: CurvedTextStyle? = null,
+    angularDirection: CurvedDirection.Angular? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+) = basicCurvedText(text, modifier, angularDirection, overflow) {
+    val baseStyle = style ?: CurvedTextStyle(LocalTextStyle.current)
+    val textColor = color.takeOrElse {
+        baseStyle.color.takeOrElse {
+            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+        }
+    }
+    baseStyle.merge(
+        CurvedTextStyle(
+            color = textColor,
+            fontSize = fontSize,
+            fontFamily = fontFamily,
+            fontWeight = fontWeight,
+            fontStyle = fontStyle,
+            fontSynthesis = fontSynthesis,
+            background = background
+        )
+    )
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
index e3c916a..6c9e8b7 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
@@ -178,12 +178,10 @@
     fun textButtonColors(
         containerColor: Color = Color.Transparent,
         contentColor: Color = MaterialTheme.colorScheme.onBackground,
-        disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.copy(
-            alpha = DisabledBorderAndContainerAlpha
+        disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+            disabledAlpha = DisabledBorderAndContainerAlpha
         ),
-        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(
-            alpha = ContentAlpha.disabled
-        )
+        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
     ): TextButtonColors = TextButtonColors(
         containerColor = containerColor,
         contentColor = contentColor,
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/current.txt b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
index cc787d5..77742f0 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
@@ -49,8 +49,8 @@
   }
 
   public interface PlatformDataProvider {
-    method public void registerForData(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
-    method public void unregisterForData();
+    method public void clearReceiver();
+    method public void setReceiver(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
   }
 
   public interface PlatformDataReceiver {
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
index a61bc5f..9904ca4 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
@@ -49,8 +49,8 @@
   }
 
   public interface PlatformDataProvider {
-    method public void registerForData(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
-    method public void unregisterForData();
+    method public void clearReceiver();
+    method public void setReceiver(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
   }
 
   public interface PlatformDataReceiver {
@@ -65,8 +65,8 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SensorGatewaySingleDataProvider implements androidx.wear.protolayout.expression.pipeline.PlatformDataProvider {
     ctor public SensorGatewaySingleDataProvider(androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway, androidx.wear.protolayout.expression.PlatformDataKey<?>);
-    method public void registerForData(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
-    method public void unregisterForData();
+    method public void clearReceiver();
+    method public void setReceiver(java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.PlatformDataReceiver);
   }
 
   public class StateStore {
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataProvider.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataProvider.java
index 6380561..51a9ee7 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataProvider.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataProvider.java
@@ -20,29 +20,39 @@
 import androidx.wear.protolayout.expression.PlatformDataKey;
 
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
  * This interface is used by platform data providers to yield dynamic data for their supported data
  * keys.
  *
- * <p> It's up to the implementations to check if the expression provider has the required
- * permission before sending data with {@link PlatformDataReceiver#onData(Map)} )}. If a required
+ * <p>It's up to the implementations to check if the expression provider has the required
+ * permission before sending data with {@link PlatformDataReceiver#onData(Map)}. If a required
  * permission is not granted or is revoked they should stop sending more data and call
- * {@link DynamicTypeValueReceiver#onInvalidated()} instead.
+ * {@link PlatformDataReceiver#onInvalidated(Set)} instead.
  */
 public interface PlatformDataProvider {
     /**
-     * Registers a callback for receiving the platform data from this provider.
+     * Sets the receiver for receiving the platform data from this provider.
      *
-     * <p> The implementation should periodically send the dynamic data values for the set of
+     * <p>Each provider is expected to have only one receiver. When a receiver has already been
+     * set, the implementation should throw an exception.
+     *
+     * <p>The implementation should periodically send the dynamic data values for the set of
      * {@link PlatformDataKey}s specified when registering this {@link PlatformDataProvider} in
      * {@link DynamicTypeEvaluator.Config.Builder#addPlatformDataProvider}
+     *
+     * @param executor The executor to run the {@link PlatformDataReceiver#onData(Map)} or
+     *                 {@link PlatformDataReceiver#onInvalidated(Set)} on.
+     * @param receiver The {@link PlatformDataReceiver} to receive the new dynamic values or to
+     *                 be notified that the current data has been invalidated for the registered
+     *                 keys.
      */
-    void registerForData(@NonNull Executor executor, @NonNull PlatformDataReceiver callback);
+    void setReceiver(@NonNull Executor executor, @NonNull PlatformDataReceiver receiver);
 
     /**
-     * Unregister from the provider.
+     * Clears the receiver from the provider.
      */
-    void unregisterForData();
+    void clearReceiver();
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataReceiver.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataReceiver.java
index f50e2a2..8a9b619 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataReceiver.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataReceiver.java
@@ -27,11 +27,20 @@
  * Callback for receiving a PlatformDataProvider's new data.
  */
 public interface PlatformDataReceiver {
+
     /**
-     * Called when the registered data provider is sending new values.
+     * Called by the registered {@ilnk PlatformDataProvider} to send new values.
+     *
+     * @param newData The new values for the registered keys.
      */
     void onData(@NonNull Map<PlatformDataKey<?>, DynamicDataValue> newData);
 
-    /** Called when the data provider has an invalid result. */
+    /**
+     * Called by the registered {@ilnk PlatformDataProvider} to notify that the current data has
+     * been invalidated. Typically, this invalidated status is transient and subsequent onData
+     * call can be followed to sent new values.
+     *
+     * @param keys The set of keys with current data been invalidated.
+     */
     void onInvalidated(@NonNull Set<PlatformDataKey<?>> keys);
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
index c90fbda..0753348 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewaySingleDataProvider.java
@@ -33,11 +33,15 @@
 /** This provider provides sensor data as state value. */
 @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
 public class SensorGatewaySingleDataProvider implements PlatformDataProvider {
-    @NonNull private final SensorGateway mSensorGateway;
-    @NonNull final PlatformDataKey<?> mSupportedKey;
-    @Nullable private SensorGateway.Consumer mSensorGatewayConsumer = null;
+    @NonNull
+    private final SensorGateway mSensorGateway;
+    @NonNull
+    final PlatformDataKey<?> mSupportedKey;
+    @Nullable
+    private SensorGateway.Consumer mSensorGatewayConsumer = null;
 
-    @NonNull Function<Double, DynamicDataValue> mConvertFunc;
+    @NonNull
+    Function<Double, DynamicDataValue> mConvertFunc;
 
     public SensorGatewaySingleDataProvider(
             @NonNull SensorGateway sensorGateway,
@@ -55,8 +59,12 @@
 
     @Override
     @SuppressWarnings("HiddenTypeParameters")
-    public void registerForData(
+    public void setReceiver(
             @NonNull Executor executor, @NonNull PlatformDataReceiver callback) {
+        if (mSensorGatewayConsumer != null) {
+            throw new RuntimeException("There is already a receiver been set.");
+        }
+
         SensorGateway.Consumer sensorConsumer =
                 new SensorGateway.Consumer() {
                     @Override
@@ -77,7 +85,7 @@
     }
 
     @Override
-    public void unregisterForData() {
+    public void clearReceiver() {
         if (mSensorGatewayConsumer != null) {
             mSensorGateway.unregisterSensorGatewayConsumer(mSupportedKey, mSensorGatewayConsumer);
             mSensorGatewayConsumer = null;
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
index bf65534..7a7b929 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
@@ -21,6 +21,7 @@
 import android.annotation.SuppressLint;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -52,6 +53,8 @@
     @SuppressLint("MinMaxConstant")
     private static final int MAX_STATE_ENTRY_COUNT = 30;
 
+    private static final String TAG = "ProtoLayoutStateStore";
+
     private final Executor mUiExecutor;
     @NonNull private final Map<AppDataKey<?>, DynamicDataValue> mCurrentAppState
             = new ArrayMap<>();
@@ -275,7 +278,7 @@
                     mProviderToRegisteredKeyCount.getOrDefault(platformDataProvider, 0);
 
             if (registeredKeyCount == 0) {
-                platformDataProvider.registerForData(
+                platformDataProvider.setReceiver(
                         mUiExecutor,
                         new PlatformDataReceiver() {
                             @Override
@@ -295,8 +298,7 @@
 
             mProviderToRegisteredKeyCount.put(platformDataProvider, registeredKeyCount + 1);
         } else {
-            throw new IllegalArgumentException(
-                    String.format("No platform data provider for %s", key));
+            Log.w(TAG, String.format("No platform data provider for %s.", key));
         }
     }
 
@@ -320,12 +322,11 @@
                 int registeredKeyCount =
                         mProviderToRegisteredKeyCount.getOrDefault(platformDataProvider, 0);
                 if (registeredKeyCount == 1) {
-                    platformDataProvider.unregisterForData();
+                    platformDataProvider.clearReceiver();
                 }
                 mProviderToRegisteredKeyCount.put(platformDataProvider, registeredKeyCount - 1);
             } else {
-                throw new IllegalArgumentException(
-                        String.format("No platform data provider for %s", key));
+                Log.w(TAG, String.format("No platform data provider for %s", key));
             }
         }
     }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
index 324d7ed..3389966 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
@@ -366,7 +366,7 @@
         }
 
         @Override
-        public void registerForData(
+        public void setReceiver(
                 @NonNull Executor executor,
                 @NonNull PlatformDataReceiver callback) {
                 mRegisterCount++;
@@ -375,7 +375,7 @@
         }
 
         @Override
-        public void unregisterForData() {
+        public void clearReceiver() {
             if (mRegisteredCallback != null) {
                 mRegisteredCallback.onInvalidated(mSupportedKeys);
                 mRegisterCount--;
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index f51a3e9..98a4b45 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -315,7 +315,7 @@
 
   public class PlatformHealthSources {
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyCalories();
-    method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyDistanceM();
+    method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyDistanceMeters();
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyFloors();
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 dailySteps();
     method @RequiresPermission(android.Manifest.permission.BODY_SENSORS) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat heartRateBpm();
@@ -323,7 +323,7 @@
 
   public static class PlatformHealthSources.Keys {
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_CALORIES;
-    field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_DISTANCE_M;
+    field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_DISTANCE_METERS;
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_FLOORS;
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32!> DAILY_STEPS;
     field @RequiresPermission(android.Manifest.permission.BODY_SENSORS) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> HEART_RATE_BPM;
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index f51a3e9..98a4b45 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -315,7 +315,7 @@
 
   public class PlatformHealthSources {
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyCalories();
-    method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyDistanceM();
+    method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyDistanceMeters();
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dailyFloors();
     method @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 dailySteps();
     method @RequiresPermission(android.Manifest.permission.BODY_SENSORS) public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat heartRateBpm();
@@ -323,7 +323,7 @@
 
   public static class PlatformHealthSources.Keys {
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_CALORIES;
-    field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_DISTANCE_M;
+    field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_DISTANCE_METERS;
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> DAILY_FLOORS;
     field @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32!> DAILY_STEPS;
     field @RequiresPermission(android.Manifest.permission.BODY_SENSORS) public static final androidx.wear.protolayout.expression.PlatformDataKey<androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat!> HEART_RATE_BPM;
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformDataKey.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformDataKey.java
index 24ed21e..b1fd7cf 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformDataKey.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformDataKey.java
@@ -22,19 +22,18 @@
 /**
  * Represent a {@link DynamicDataKey} that references real-time data from the platform.
  *
- * <p> The [namespace, key] tuple creates the actual reference, so that a single key can refer to
- * two different sources in two different namespaces.
+ * <p>The [namespace, key] tuple creates the actual reference, so that a single key can refer to two
+ * different sources in two different namespaces.
  *
- * <p> The namespace must not be empty. Additionally, the "protolayout" namespace (and its
- * lowercase and uppercase variations) are reserved for the default platform data sources and
- * should not be used for any custom OEM data source. To choose the namespace that does not
- * conflict with an existing one, use a unique prefix for your namespace, for example, company
- * name or product name.
+ * <p>The namespace must not be empty. Additionally, the "protolayout" namespace (and its lowercase
+ * and uppercase variations) are reserved for the default platform data sources and should not be
+ * used for any custom OEM data source. To make sure namespaces are unique, any custom namespace is
+ * expected to follow Java style naming {@code com.company.foo}.
  *
  * @param <T> The data type of the dynamic values that this key is bound to.
  */
-public final class PlatformDataKey<T extends DynamicBuilders.DynamicType> extends DynamicDataKey<T>
-{
+public final class PlatformDataKey<T extends DynamicBuilders.DynamicType>
+        extends DynamicDataKey<T> {
     @NonNull private static final String RESERVED_NAMESPACE = "protolayout";
 
     /**
@@ -50,15 +49,16 @@
         }
 
         if (RESERVED_NAMESPACE.equalsIgnoreCase(namespace)) {
-            throw new IllegalArgumentException(String.format(
-                    "Custom data source must not use the reserved namespace:%s",
-                    RESERVED_NAMESPACE));
+            throw new IllegalArgumentException(
+                    String.format(
+                            "Custom data source must not use the reserved namespace:%s",
+                            RESERVED_NAMESPACE));
         }
     }
 
     /**
-     * Create a {@link PlatformDataKey} with the specified key in the reserved namespace.
-     * This should only be used by protolayout library internally for default platform data sources.
+     * Create a {@link PlatformDataKey} with the specified key in the reserved namespace. This
+     * should only be used by protolayout library internally for default platform data sources.
      *
      * @param key The key that references the platform data source
      */
@@ -67,4 +67,3 @@
         super(RESERVED_NAMESPACE, key);
     }
 }
-
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
index 0214ed2..e5b2be5 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
@@ -58,7 +58,7 @@
          */
         @NonNull
         @RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
-        public static final PlatformDataKey<DynamicFloat> DAILY_DISTANCE_M =
+        public static final PlatformDataKey<DynamicFloat> DAILY_DISTANCE_METERS =
                 new PlatformDataKey<>("Daily Distance");
 
         /**
@@ -153,7 +153,7 @@
      */
     @RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
     @NonNull
-    public static DynamicFloat dailyDistanceM() {
-        return DynamicFloat.from(Keys.DAILY_DISTANCE_M);
+    public static DynamicFloat dailyDistanceMeters() {
+        return DynamicFloat.from(Keys.DAILY_DISTANCE_METERS);
     }
 }
diff --git a/wear/protolayout/protolayout-material/api/current.txt b/wear/protolayout/protolayout-material/api/current.txt
index 64cf2bf..f5941b8 100644
--- a/wear/protolayout/protolayout-material/api/current.txt
+++ b/wear/protolayout/protolayout-material/api/current.txt
@@ -137,12 +137,14 @@
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
     method public String getText();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class CompactChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.CompactChip.Builder setExcludeFontPadding(boolean);
   }
 
   public class ProgressIndicatorColors {
@@ -163,7 +165,6 @@
   public class Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public static androidx.wear.protolayout.material.Text? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
-    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean getExcludeFontPadding();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle getFontStyle();
     method public float getLineHeight();
     method public int getMaxLines();
@@ -172,6 +173,7 @@
     method public int getOverflow();
     method public androidx.wear.protolayout.TypeBuilders.StringProp getText();
     method public int getWeight();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
     method public boolean isItalic();
     method public boolean isUnderline();
   }
@@ -199,12 +201,14 @@
     method public int getHorizontalAlignment();
     method public String getText();
     method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class TitleChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.TitleChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
@@ -255,10 +259,10 @@
   }
 
   public static final class LayoutDefaults.MultiButtonLayoutDefaults {
-    field public static final int BUTTON_MAX_NUMBER = 7; // 0x7
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_1_BUTTON;
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_2_BUTTONS;
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_3_PLUS_BUTTONS;
+    field public static final int MAX_BUTTONS = 7; // 0x7
   }
 
   public class MultiButtonLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
diff --git a/wear/protolayout/protolayout-material/api/restricted_current.txt b/wear/protolayout/protolayout-material/api/restricted_current.txt
index 64cf2bf..f5941b8 100644
--- a/wear/protolayout/protolayout-material/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material/api/restricted_current.txt
@@ -137,12 +137,14 @@
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
     method public String getText();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class CompactChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.CompactChip.Builder setExcludeFontPadding(boolean);
   }
 
   public class ProgressIndicatorColors {
@@ -163,7 +165,6 @@
   public class Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public static androidx.wear.protolayout.material.Text? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
-    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean getExcludeFontPadding();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle getFontStyle();
     method public float getLineHeight();
     method public int getMaxLines();
@@ -172,6 +173,7 @@
     method public int getOverflow();
     method public androidx.wear.protolayout.TypeBuilders.StringProp getText();
     method public int getWeight();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
     method public boolean isItalic();
     method public boolean isUnderline();
   }
@@ -199,12 +201,14 @@
     method public int getHorizontalAlignment();
     method public String getText();
     method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class TitleChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.TitleChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
@@ -255,10 +259,10 @@
   }
 
   public static final class LayoutDefaults.MultiButtonLayoutDefaults {
-    field public static final int BUTTON_MAX_NUMBER = 7; // 0x7
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_1_BUTTON;
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_2_BUTTONS;
     field public static final androidx.wear.protolayout.DimensionBuilders.DpProp BUTTON_SIZE_FOR_3_PLUS_BUTTONS;
+    field public static final int MAX_BUTTONS = 7; // 0x7
   }
 
   public class MultiButtonLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
index 3653dc9..f508a0c 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
@@ -208,36 +208,53 @@
         // more than 9, the rest will be deleted.
         testCases.put(
                 "compactchip_default_len2_golden" + goldenSuffix,
-                new CompactChip.Builder(context, "Ab", clickable, deviceParameters).build());
+                new CompactChip.Builder(context, "Ab", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
+                        .build());
         testCases.put(
                 "compactchip_default_len5_golden" + goldenSuffix,
-                new CompactChip.Builder(context, "Abcde", clickable, deviceParameters).build());
+                new CompactChip.Builder(context, "Abcde", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
+                        .build());
         testCases.put(
                 "compactchip_default_len9_golden" + goldenSuffix,
-                new CompactChip.Builder(context, "Abcdefghi", clickable, deviceParameters).build());
+                new CompactChip.Builder(context, "Abcdefghi", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
+                        .build());
         testCases.put(
                 "compactchip_default_toolong_golden" + goldenSuffix,
                 new CompactChip.Builder(
                                 context, "AbcdefghiEXTRAEXTRAEXTRA", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_custom_default_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
+                        .setExcludeFontPadding(true)
+                        .build());
+        testCases.put(
+                "compactchip_includepadding_default_golden" + goldenSuffix,
+                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
+                        .setExcludeFontPadding(false)
                         .build());
 
         testCases.put(
                 "titlechip_default_golden" + goldenSuffix,
-                new TitleChip.Builder(context, largeChipText, clickable, deviceParameters).build());
+                new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
+                        .build());
         testCases.put(
                 "titlechip_default_texttoolong_golden" + goldenSuffix,
                 new TitleChip.Builder(context, "abcdeabcdeabcdeEXTRA", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "titlechip_leftalign_secondary_default_golden" + goldenSuffix,
                 new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
                         .setHorizontalAlignment(HORIZONTAL_ALIGN_START)
                         .setChipColors(ChipDefaults.TITLE_SECONDARY_COLORS)
+                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "titlechip_centered_custom_150_secondary_default_golden" + goldenSuffix,
@@ -245,6 +262,12 @@
                         .setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLUE))
                         .setWidth(150)
+                        .setExcludeFontPadding(true)
+                        .build());
+        testCases.put(
+                "titlechip_includepadding_default_golden" + goldenSuffix,
+                new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
+                        .setExcludeFontPadding(false)
                         .build());
 
         testCases.put(
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
index e95c9da..02bd273 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
@@ -75,9 +75,12 @@
         HashMap<String, LayoutElement> testCases = new HashMap<>();
 
         TitleChip content =
-                new TitleChip.Builder(context, "Action", clickable, deviceParameters).build();
+                new TitleChip.Builder(context, "Action", clickable, deviceParameters)
+                        .setExcludeFontPadding(true)
+                        .build();
         CompactChip.Builder primaryChipBuilder =
-                new CompactChip.Builder(context, "Action", clickable, deviceParameters);
+                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
+                        .setExcludeFontPadding(true);
 
         testCases.put(
                 "default_empty_primarychiplayout_golden" + goldenSuffix,
@@ -93,6 +96,7 @@
                                                 "Too_long_textToo_long_textToo_long_text",
                                                 clickable,
                                                 deviceParameters)
+                                        .setExcludeFontPadding(true)
                                         .build())
                         .build());
         testCases.put(
@@ -162,7 +166,8 @@
                         .build());
 
         primaryChipBuilder =
-                new CompactChip.Builder(context, "Action", clickable, deviceParameters);
+                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
+                        .setExcludeFontPadding(true);
         testCases.put(
                 "coloredbox_1_chip_columnslayout_golden" + goldenSuffix,
                 new PrimaryLayout.Builder(deviceParameters)
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
index d4e15b1..d0efe27 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
@@ -26,6 +26,8 @@
 import static androidx.wear.protolayout.material.ChipDefaults.HORIZONTAL_PADDING;
 import static androidx.wear.protolayout.material.ChipDefaults.ICON_SIZE;
 import static androidx.wear.protolayout.material.ChipDefaults.ICON_SPACER_WIDTH;
+import static androidx.wear.protolayout.material.ChipDefaults.MIN_TAPPABLE_HEIGHT;
+import static androidx.wear.protolayout.material.ChipDefaults.MIN_TAPPABLE_WIDTH;
 import static androidx.wear.protolayout.material.ChipDefaults.PRIMARY_COLORS;
 import static androidx.wear.protolayout.material.Helper.checkNotNull;
 import static androidx.wear.protolayout.material.Helper.checkTag;
@@ -33,18 +35,22 @@
 import static androidx.wear.protolayout.material.Helper.getTagBytes;
 import static androidx.wear.protolayout.material.Helper.radiusOf;
 
+import static java.lang.Math.max;
+
 import android.content.Context;
 
 import androidx.annotation.Dimension;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.ColorBuilders.ColorProp;
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.DimensionBuilders.ContainerDimension;
 import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp;
 import androidx.wear.protolayout.LayoutElementBuilders;
 import androidx.wear.protolayout.LayoutElementBuilders.Box;
 import androidx.wear.protolayout.LayoutElementBuilders.ColorFilter;
@@ -63,6 +69,7 @@
 import androidx.wear.protolayout.ModifiersBuilders.Semantics;
 import androidx.wear.protolayout.TypeBuilders.StringProp;
 import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.material.Typography.TypographyName;
 import androidx.wear.protolayout.proto.LayoutElementProto;
 
@@ -75,10 +82,11 @@
  * ProtoLayout component {@link Chip} that represents clickable object with the text, optional label
  * and optional icon or with custom content.
  *
- * <p>The Chip is Stadium shape and has a max height designed to take no more than two lines of text
- * of {@link Typography#TYPOGRAPHY_BUTTON} style. The {@link Chip} can have an icon horizontally
- * parallel to the two lines of text. Width of chip can very, and the recommended size is screen
- * dependent with the recommended margin being applied.
+ * <p>The Chip is Stadium shape that has a max height designed to take no more than two lines of
+ * text of {@link Typography#TYPOGRAPHY_BUTTON} style and with minimum tap target to meet
+ * accessibility requirements. The {@link Chip} can have an icon horizontally parallel to the two
+ * lines of text. Width of chip can very, and the recommended size is screen dependent with the
+ * recommended margin being applied.
  *
  * <p>The recommended set of {@link ChipColors} styles can be obtained from {@link ChipDefaults}.,
  * e.g. {@link ChipDefaults#PRIMARY_COLORS} to get a color scheme for a primary {@link Chip}.
@@ -118,11 +126,17 @@
      */
     static final String METADATA_TAG_CUSTOM_CONTENT = "CSTCHP";
 
+    /** Outer tappable Box. */
+    @NonNull private final Box mImpl;
+
+    /** Inner visible Box with all Chip elements. */
     @NonNull private final Box mElement;
 
-    Chip(@NonNull Box element) {
-        mElement = element;
+    Chip(@NonNull Box impl) {
+        mImpl = impl;
+        mElement = (Box) impl.getContents().get(0);
     }
+
     /** Builder class for {@link androidx.wear.protolayout.material.Chip}. */
     public static final class Builder implements LayoutElement.Builder {
         private static final int NOT_SET = 0;
@@ -149,6 +163,7 @@
         @TypographyName private int mPrimaryLabelTypography;
         @NonNull private DpProp mHorizontalPadding = HORIZONTAL_PADDING;
         private boolean mIsScalable = true;
+        private boolean mIsFontPaddingExcluded = false;
         private int mMaxLines = 0; // 0 indicates that is not set.
         @NonNull private String mMetadataTag = "";
 
@@ -274,6 +289,22 @@
         }
 
         /**
+         * Sets whether the font padding for the primary label is excluded.
+         *
+         * <p>It should be used for creating CompactChip and TitleChip to make the label vertically
+         * aligned. Shouldn't be used if there is anything else in chip besides primary label.
+         *
+         * @see Text.Builder#setExcludeFontPadding
+         */
+        @NonNull
+        @ProtoLayoutExperimental
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        Builder setPrimaryLabelExcludeFontPadding(boolean excluded) {
+            this.mIsFontPaddingExcluded = excluded;
+            return this;
+        }
+
+        /**
          * Sets the secondary label for the {@link Chip}. Any previously added custom content will
          * be overridden. If secondary label is set, primary label must be set too with {@link
          * #setPrimaryLabelContent}.
@@ -357,7 +388,6 @@
         public Chip build() {
             Modifiers.Builder modifiers =
                     new Modifiers.Builder()
-                            .setClickable(mClickable)
                             .setPadding(
                                     new Padding.Builder()
                                             .setStart(mHorizontalPadding)
@@ -370,25 +400,56 @@
                                                     new Corner.Builder()
                                                             .setRadius(radiusOf(mHeight))
                                                             .build())
-                                            .build())
-                            .setMetadata(
-                                    new ElementMetadata.Builder()
-                                            .setTagData(getTagBytes(getCorrectMetadataTag()))
-                                            .build())
-                            .setSemantics(
-                                    new Semantics.Builder()
-                                            .setContentDescription(getCorrectContentDescription())
                                             .build());
 
-            Box.Builder element =
+            Box.Builder visible =
                     new Box.Builder()
-                            .setWidth(mWidth)
                             .setHeight(mHeight)
+                            .setWidth(mWidth)
                             .setHorizontalAlignment(getCorrectHorizontalAlignment())
                             .addContent(getCorrectContent())
                             .setModifiers(modifiers.build());
 
-            return new Chip(element.build());
+            Box tappable =
+                    new Box.Builder()
+                            .setWidth(resolveMinTappableWidth())
+                            .setHeight(dp(resolveMinTappableHeight()))
+                            .setModifiers(
+                                    new Modifiers.Builder()
+                                            .setClickable(mClickable)
+                                            .setMetadata(
+                                                    new ElementMetadata.Builder()
+                                                            .setTagData(
+                                                                    getTagBytes(
+                                                                            getCorrectMetadataTag())
+                                                            )
+                                                            .build())
+                                            .setSemantics(
+                                                    new Semantics.Builder()
+                                                            .setContentDescription(
+                                                                    getCorrectContentDescription())
+                                                            .build())
+                                            .build())
+                            .addContent(visible.build())
+                            .build();
+
+            return new Chip(tappable);
+        }
+
+        private ContainerDimension resolveMinTappableWidth() {
+            if (mWidth instanceof DpProp) {
+                return dp(max(((DpProp) mWidth).getValue(), MIN_TAPPABLE_WIDTH.getValue()));
+            } else if (mWidth instanceof WrappedDimensionProp) {
+                return new WrappedDimensionProp.Builder()
+                        .setMinimumSize(MIN_TAPPABLE_WIDTH)
+                        .build();
+            } else {
+                return mWidth;
+            }
+        }
+
+        private float resolveMinTappableHeight() {
+            return max(mHeight.getValue(), MIN_TAPPABLE_HEIGHT.getValue());
         }
 
         @NonNull
@@ -434,6 +495,7 @@
         }
 
         @NonNull
+        @OptIn(markerClass = ProtoLayoutExperimental.class)
         private LayoutElement getCorrectContent() {
             if (mCustomContent != null) {
                 return mCustomContent;
@@ -447,6 +509,7 @@
                             .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
                             .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_START)
                             .setIsScalable(mIsScalable)
+                            .setExcludeFontPadding(mIsFontPaddingExcluded)
                             .build();
 
             // Placeholder for text.
@@ -506,7 +569,7 @@
         }
     }
 
-    /** Returns height of this Chip. */
+    /** Returns the visible height of this Chip. */
     @NonNull
     public ContainerDimension getHeight() {
         return checkNotNull(mElement.getHeight());
@@ -521,7 +584,7 @@
     /** Returns click event action associated with this Chip. */
     @NonNull
     public Clickable getClickable() {
-        return checkNotNull(checkNotNull(mElement.getModifiers()).getClickable());
+        return checkNotNull(checkNotNull(mImpl.getModifiers()).getClickable());
     }
 
     /** Returns background color of this Chip. */
@@ -569,7 +632,7 @@
     /** Returns content description of this Chip. */
     @Nullable
     public StringProp getContentDescription() {
-        Semantics semantics = checkNotNull(mElement.getModifiers()).getSemantics();
+        Semantics semantics = checkNotNull(mImpl.getModifiers()).getSemantics();
         if (semantics == null) {
             return null;
         }
@@ -659,8 +722,16 @@
     /** Returns metadata tag set to this Chip. */
     @NonNull
     String getMetadataTag() {
-        return getMetadataTagName(
-                checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+        return getMetadataTagName(checkNotNull(checkNotNull(mImpl.getModifiers()).getMetadata()));
+    }
+
+    /**
+     *  Returns whether the font padding for the primary label is excluded.
+     */
+    @ProtoLayoutExperimental
+    boolean hasPrimaryLabelExcludeFontPadding() {
+        Text primaryLabel = getPrimaryLabelContentObject();
+        return primaryLabel != null && primaryLabel.hasExcludeFontPadding();
     }
 
     /**
@@ -688,13 +759,13 @@
     @Override
     @RestrictTo(Scope.LIBRARY_GROUP)
     public LayoutElementProto.LayoutElement toLayoutElementProto() {
-        return mElement.toLayoutElementProto();
+        return mImpl.toLayoutElementProto();
     }
 
     @Nullable
     @Override
     @RestrictTo(Scope.LIBRARY_GROUP)
     public Fingerprint getFingerprint() {
-        return mElement.getFingerprint();
+        return mImpl.getFingerprint();
     }
 }
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
index 2139050..36033ab 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
@@ -44,6 +44,20 @@
     public static final DpProp COMPACT_HEIGHT = dp(32);
 
     /**
+     * The minimum width of tappable target area.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static final DpProp MIN_TAPPABLE_WIDTH = dp(48);
+
+    /**
+     * The minimum height of tappable target area.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static final DpProp MIN_TAPPABLE_HEIGHT = dp(48);
+
+    /**
      * The default height of tappable area for standard {@link CompactChip}
      *
      */
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
index 840aa47..d63048b 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
@@ -30,6 +30,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
@@ -40,6 +41,7 @@
 import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
 import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
 import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.proto.LayoutElementProto;
 
 /**
@@ -92,6 +94,7 @@
         @NonNull private final Clickable mClickable;
         @NonNull private final DeviceParameters mDeviceParameters;
         @NonNull private ChipColors mChipColors = COMPACT_PRIMARY_COLORS;
+        private boolean mIsFontPaddingExcluded = false;
 
         /**
          * Creates a builder for the {@link CompactChip} with associated action and the given text
@@ -125,9 +128,24 @@
             return this;
         }
 
+        /**
+         * Sets whether the font padding is excluded or not. If not set, default to false, meaning
+         * that text will have font padding included.
+         *
+         * <p>Setting this to {@code true} will perfectly align the text label.
+         */
+        @NonNull
+        @ProtoLayoutExperimental
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder setExcludeFontPadding(boolean excluded) {
+            this.mIsFontPaddingExcluded = excluded;
+            return this;
+        }
+
         /** Constructs and returns {@link CompactChip} with the provided content and look. */
         @NonNull
         @Override
+        @OptIn(markerClass = ProtoLayoutExperimental.class)
         public CompactChip build() {
             Chip.Builder chipBuilder =
                     new Chip.Builder(mContext, mClickable, mDeviceParameters)
@@ -141,6 +159,7 @@
                             .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING)
                             .setPrimaryLabelContent(mText)
                             .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
+                            .setPrimaryLabelExcludeFontPadding(mIsFontPaddingExcluded)
                             .setIsPrimaryLabelScalable(false);
 
             Box tappableChip =
@@ -218,6 +237,14 @@
         return new CompactChip(boxElement);
     }
 
+    /**
+     *  Returns whether the font padding for the primary label is excluded.
+     */
+    @ProtoLayoutExperimental
+    public boolean hasExcludeFontPadding() {
+        return mElement.hasPrimaryLabelExcludeFontPadding();
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
index c9d8575..eb8d641 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
@@ -25,7 +25,6 @@
 import static androidx.wear.protolayout.material.Typography.getFontStyleBuilder;
 import static androidx.wear.protolayout.material.Typography.getLineHeightForTypography;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 
 import androidx.annotation.IntRange;
@@ -230,8 +229,8 @@
         // other impacted UI Libraries - needs care as will have an impact on layout and needs to be
         // communicated clearly.
         @NonNull
-        @SuppressLint("MissingGetterMatchingBuilder")
         @ProtoLayoutExperimental
+        @SuppressWarnings("MissingGetterMatchingBuilder")
         public Builder setExcludeFontPadding(boolean excludeFontPadding) {
             this.mElementBuilder.setAndroidTextStyle(
                     new LayoutElementBuilders.AndroidTextStyle.Builder()
@@ -326,7 +325,7 @@
      * excluded.
      */
     @ProtoLayoutExperimental
-    public boolean getExcludeFontPadding() {
+    public boolean hasExcludeFontPadding() {
         return checkNotNull(mText.getAndroidTextStyle()).getExcludeFontPadding();
     }
 
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
index dc7ee6e..46e2cec 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
@@ -30,6 +30,7 @@
 import androidx.annotation.Dimension;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
@@ -39,6 +40,7 @@
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
 import androidx.wear.protolayout.ModifiersBuilders.Clickable;
 import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.proto.LayoutElementProto;
 
 /**
@@ -95,6 +97,7 @@
         // Indicates that the width isn't set, so it will be automatically set by Chip.Builder
         // constructor.
         @Nullable private ContainerDimension mWidth = null;
+        private boolean mIsFontPaddingExcluded = false;
 
         /**
          * Creates a builder for the {@link TitleChip} with associated action and the given text
@@ -156,9 +159,24 @@
             return this;
         }
 
+        /**
+         * Sets whether the font padding is excluded or not. If not set, default to false, meaning
+         * that text will have font padding included.
+         *
+         * <p>Setting this to {@code true} will perfectly align the text label.
+         */
+        @NonNull
+        @ProtoLayoutExperimental
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder setExcludeFontPadding(boolean excluded) {
+            this.mIsFontPaddingExcluded = excluded;
+            return this;
+        }
+
         /** Constructs and returns {@link TitleChip} with the provided content and look. */
         @NonNull
         @Override
+        @OptIn(markerClass = ProtoLayoutExperimental.class)
         public TitleChip build() {
             Chip.Builder chipBuilder =
                     new Chip.Builder(mContext, mClickable, mDeviceParameters)
@@ -171,6 +189,7 @@
                             .setHorizontalPadding(TITLE_HORIZONTAL_PADDING)
                             .setPrimaryLabelContent(mText)
                             .setPrimaryLabelTypography(Typography.TYPOGRAPHY_TITLE2)
+                            .setPrimaryLabelExcludeFontPadding(mIsFontPaddingExcluded)
                             .setIsPrimaryLabelScalable(false);
 
             if (mWidth != null) {
@@ -238,6 +257,14 @@
         return new TitleChip(new Chip(boxElement));
     }
 
+    /**
+     *  Returns whether the font padding for the primary label is excluded.
+     */
+    @ProtoLayoutExperimental
+    public boolean hasExcludeFontPadding() {
+        return mElement.hasPrimaryLabelExcludeFontPadding();
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
index 0f1d947..8afa1e2 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
@@ -110,7 +110,7 @@
     /**
      * The maximum number of button that can be added to the {@link MultiButtonLayout}.
      *
-     * @deprecated Use {@link MultiButtonLayoutDefaults#BUTTON_MAX_NUMBER} instead.
+     * @deprecated Use {@link MultiButtonLayoutDefaults#MAX_BUTTONS} instead.
      */
     @Deprecated public static final int MULTI_BUTTON_MAX_NUMBER = 7;
 
@@ -120,7 +120,8 @@
         }
 
         /** The maximum number of button that can be added to the {@link MultiButtonLayout}. */
-        public static final int BUTTON_MAX_NUMBER = 7;
+        @SuppressWarnings("MinMaxConstant")
+        public static final int MAX_BUTTONS = 7;
 
         /**
          * The default size of button in case when there are 3 or more buttons in the {@link
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java
index 28c88dc..26617e3 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java
@@ -21,10 +21,10 @@
 import static androidx.wear.protolayout.material.Helper.checkTag;
 import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
 import static androidx.wear.protolayout.material.Helper.getTagBytes;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.BUTTON_MAX_NUMBER;
 import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.BUTTON_SIZE_FOR_1_BUTTON;
 import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.BUTTON_SIZE_FOR_2_BUTTONS;
 import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.BUTTON_SIZE_FOR_3_PLUS_BUTTONS;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.MAX_BUTTONS;
 import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.SPACER_HEIGHT;
 import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MultiButtonLayoutDefaults.SPACER_WIDTH;
 
@@ -52,10 +52,10 @@
 
 /**
  * Opinionated ProtoLayout layout, that can contain between 1 and {@link
- * LayoutDefaults.MultiButtonLayoutDefaults#BUTTON_MAX_NUMBER} number of buttons arranged
+ * LayoutDefaults.MultiButtonLayoutDefaults#MAX_BUTTONS} number of buttons arranged
  * inline with the Material
  * guidelines. Can be used as a content passed in to the {@link PrimaryLayout}, but if there is
- * {@link LayoutDefaults.MultiButtonLayoutDefaults#BUTTON_MAX_NUMBER} buttons it should be used
+ * {@link LayoutDefaults.MultiButtonLayoutDefaults#MAX_BUTTONS} buttons it should be used
  * on its own.
  *
  * <p>When accessing the contents of a container for testing, note that this element can't be simply
@@ -115,7 +115,7 @@
          * Add one new button to the layout. Note that it is accepted to pass in any {@link
          * LayoutElement}, but it is strongly recommended to add a {@link Button} as the layout is
          * optimized for it. Any button added after
-         * {@link LayoutDefaults.MultiButtonLayoutDefaults#BUTTON_MAX_NUMBER} is reached will be
+         * {@link LayoutDefaults.MultiButtonLayoutDefaults#MAX_BUTTONS} is reached will be
          * discarded.
          */
         @NonNull
@@ -143,10 +143,10 @@
         @Override
         public MultiButtonLayout build() {
             int buttonNum = mButtonsContent.size();
-            if (buttonNum > BUTTON_MAX_NUMBER) {
+            if (buttonNum > MAX_BUTTONS) {
                 throw new IllegalArgumentException(
                         "Too many buttons are added. Maximum number is "
-                                + BUTTON_MAX_NUMBER
+                                + MAX_BUTTONS
                                 + ".");
             }
 
@@ -249,10 +249,10 @@
                                             mButtonsContent.get(6),
                                             BUTTON_SIZE_FOR_3_PLUS_BUTTONS))
                             .build();
+                default:
+                    throw new IllegalArgumentException(
+                            "Too many buttons are added. Maximum number is " + MAX_BUTTONS + ".");
             }
-            // This shouldn't happen, but return an empty Box instead of having this method nullable
-            // and checks above.
-            return new Box.Builder().build();
         }
 
         @NonNull
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
index a9957f4..ea6f7b9 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
@@ -215,7 +215,7 @@
         assertThat(actualText.getMaxLines()).isEqualTo(2);
         assertThat(actualText.getLineHeight())
                 .isEqualTo(getLineHeightForTypography(TYPOGRAPHY_TITLE1).getValue());
-        assertThat(actualText.getExcludeFontPadding()).isTrue();
+        assertThat(actualText.hasExcludeFontPadding()).isTrue();
     }
 
     private void assertFontStyle(
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/MultiButtonLayoutTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/MultiButtonLayoutTest.java
index c90bb91..d40f264 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/MultiButtonLayoutTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/MultiButtonLayoutTest.java
@@ -106,7 +106,7 @@
     public void test_too_many_button() {
         Button button = new Button.Builder(CONTEXT, CLICKABLE).setTextContent("1").build();
         MultiButtonLayout.Builder layoutBuilder = new MultiButtonLayout.Builder();
-        for (int i = 0; i < LayoutDefaults.MultiButtonLayoutDefaults.BUTTON_MAX_NUMBER + 1; i++) {
+        for (int i = 0; i < LayoutDefaults.MultiButtonLayoutDefaults.MAX_BUTTONS + 1; i++) {
             layoutBuilder.addButtonContent(button);
         }
 
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataEvaluator.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataEvaluator.kt
index c26aa5e..1aece6cf 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataEvaluator.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataEvaluator.kt
@@ -24,16 +24,13 @@
 import androidx.annotation.RestrictTo
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
 import androidx.wear.protolayout.expression.PlatformDataKey
-import androidx.wear.protolayout.expression.PlatformHealthSources
 import androidx.wear.protolayout.expression.pipeline.BoundDynamicType
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeBindingRequest
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeEvaluator
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeValueReceiver
-import androidx.wear.protolayout.expression.pipeline.SensorGatewaySingleDataProvider
+import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider
 import androidx.wear.protolayout.expression.pipeline.StateStore
 import androidx.wear.protolayout.expression.pipeline.TimeGateway
-import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway
-import java.util.Collections
 import java.util.concurrent.Executor
 import kotlin.coroutines.ContinuationInterceptor
 import kotlin.coroutines.CoroutineContext
@@ -60,8 +57,7 @@
 class ComplicationDataEvaluator(
     private val stateStore: StateStore? = StateStore(emptyMap()),
     private val timeGateway: TimeGateway? = null,
-    // TODO(b/281664278): remove the SensorGateway usage, implement PlatformDataProvider instead.
-    private val sensorGateway: SensorGateway? = null,
+    private val platformDataProviders: Map<PlatformDataProvider, Set<PlatformDataKey<*>>> = mapOf(),
     private val keepDynamicValues: Boolean = false,
 ) {
     /**
@@ -302,23 +298,8 @@
                         .apply { stateStore?.let { setStateStore(it) } }
                         .apply { timeGateway?.let { setTimeGateway(it) } }
                         .apply {
-                            sensorGateway?.let {
-                                addPlatformDataProvider(
-                                    SensorGatewaySingleDataProvider(
-                                        sensorGateway,
-                                        PlatformHealthSources.Keys.HEART_RATE_BPM
-                                    ),
-                                    Collections.singleton(PlatformHealthSources.Keys.HEART_RATE_BPM)
-                                        as Set<PlatformDataKey<*>>
-                                )
-                                addPlatformDataProvider(
-                                    SensorGatewaySingleDataProvider(
-                                        sensorGateway,
-                                        PlatformHealthSources.Keys.DAILY_STEPS
-                                    ),
-                                    Collections.singleton(PlatformHealthSources.Keys.DAILY_STEPS)
-                                        as Set<PlatformDataKey<*>>
-                                )
+                            for ((platformDataProvider, dataKeys) in platformDataProviders) {
+                                addPlatformDataProvider(platformDataProvider, dataKeys)
                             }
                         }
                         .build()
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index a7130b9..9df1d3b 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -123,7 +123,10 @@
                 addAction(WatchFaceImpl.MOCK_TIME_INTENT)
                 addAction(ACTION_AMBIENT_STARTED)
                 addAction(ACTION_AMBIENT_STOPPED)
-            }
+            },
+            // Listen to broadcasts from the system or the app itself,
+            // so it does not have to be exported
+            Context.RECEIVER_NOT_EXPORTED
         )
     }
 
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index dcdf0c2..b283da8 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -121,6 +121,7 @@
      * be reflected in that test as necessary. See http://go/modifying-webview-cts.
      */
     @Test
+    @SuppressWarnings("deprecation")
     public void testSuppressedErrorPage() throws Throwable {
         WebkitUtils.checkFeature(WebViewFeature.SUPPRESS_ERROR_PAGE);
 
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
index b8ae25db..0ed9655 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
@@ -106,6 +106,7 @@
     }
 
     @Test
+    @SuppressWarnings("deprecation") // To be removed in 1.9.0
     public void testAddDocumentStartJavaScriptBasicUsage() throws Exception {
         mWebViewOnUiThread.addWebMessageListener(JS_OBJECT_NAME, MATCH_EXAMPLE_COM, mListener);
         mWebViewOnUiThread.addDocumentStartJavaScript(BASIC_SCRIPT, MATCH_EXAMPLE_COM);
@@ -118,6 +119,7 @@
     }
 
     @Test
+    @SuppressWarnings("deprecation") // To be removed in 1.9.0
     public void testAddDocumentStartJavaScriptRemoveScript() throws Exception {
         mWebViewOnUiThread.addWebMessageListener(JS_OBJECT_NAME, MATCH_EXAMPLE_COM, mListener);
         ScriptHandler scriptHandler =
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index f28c63c..69f6cd5 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -272,6 +272,8 @@
     }
 
     @NonNull
+    @Deprecated
+    @SuppressWarnings("deprecation") // To be removed in 1.9.0
     public ScriptHandler addDocumentStartJavaScript(
             @NonNull String script, @NonNull Set<String> allowedOriginRules) {
         return WebkitUtils.onMainThreadSync(() -> WebViewCompat.addDocumentStartJavaScript(
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 595d73c..5c25673f 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -231,9 +231,9 @@
      * returns true for {@link WebViewFeature#SUPPRESS_ERROR_PAGE}.
      *
      * @param suppressed whether the WebView should suppress its internal error page
-     *
-     * TODO(cricke): unhide
+     * @deprecated unreleased API will be removed in 1.9.0
      */
+    @Deprecated
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @RequiresFeature(name = WebViewFeature.SUPPRESS_ERROR_PAGE,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
@@ -258,9 +258,9 @@
      *
      * @return true if the WebView will suppress its internal error page
      * @see #setWillSuppressErrorPage
-     *
-     * TODO(cricke): unhide
+     * @deprecated unreleased API will be removed in 1.9.0
      */
+    @Deprecated
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @RequiresFeature(name = WebViewFeature.SUPPRESS_ERROR_PAGE,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index d110b42..d3bf981 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -796,13 +796,13 @@
      * @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
      * @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
      * @see ScriptHandler
-     *
-     * TODO(swestphal): unhide when ready.
+     * @deprecated unreleased API will be removed in 1.9.0
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresFeature(
             name = WebViewFeature.DOCUMENT_START_SCRIPT,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+    @Deprecated
     public static @NonNull ScriptHandler addDocumentStartJavaScript(
             @NonNull WebView webview,
             @NonNull String script,