Merge "Clarify startActivityAndWait constraints" into androidx-main
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
index ca3a49b..9b72c8c 100644
--- a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
+++ b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
@@ -41,8 +41,8 @@
  *
  * Example of requiring separate read and write permissions for a content provider:
  * ```
- * @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
- * @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ * @RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS))
+ * @RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS))
  * public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
  * ```
  *
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 6ebee26..8ec1c3d 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -94,6 +94,7 @@
 
   public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
     ctor public AppSearchBatchResult.Builder();
+    ctor public AppSearchBatchResult.Builder(androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!>);
     method public androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!> build();
     method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setFailure(KeyType, int, String?);
     method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setResult(KeyType, androidx.appsearch.app.AppSearchResult<ValueType!>);
@@ -146,11 +147,15 @@
   }
 
   public static final class AppSearchSchema.Builder {
+    ctor public AppSearchSchema.Builder(androidx.appsearch.app.AppSearchSchema);
     ctor public AppSearchSchema.Builder(String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
     method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
     method public androidx.appsearch.app.AppSearchSchema build();
+    method public androidx.appsearch.app.AppSearchSchema.Builder clearParentTypes();
+    method public androidx.appsearch.app.AppSearchSchema.Builder clearProperties();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) public androidx.appsearch.app.AppSearchSchema.Builder setDescription(String);
+    method public androidx.appsearch.app.AppSearchSchema.Builder setSchemaType(String);
   }
 
   public static final class AppSearchSchema.BytesPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
@@ -406,9 +411,13 @@
 
   public static final class GetSchemaResponse.Builder {
     ctor public GetSchemaResponse.Builder();
+    ctor public GetSchemaResponse.Builder(androidx.appsearch.app.GetSchemaResponse);
     method public androidx.appsearch.app.GetSchemaResponse.Builder addSchema(androidx.appsearch.app.AppSearchSchema);
     method public androidx.appsearch.app.GetSchemaResponse.Builder addSchemaTypeNotDisplayedBySystem(String);
     method public androidx.appsearch.app.GetSchemaResponse build();
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibilityConfig(String);
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibilityConfigs();
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemas();
     method public androidx.appsearch.app.GetSchemaResponse.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier);
     method public androidx.appsearch.app.GetSchemaResponse.Builder setRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.util.Set<java.lang.Integer!>!>);
     method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToConfigs(String, java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>);
@@ -443,9 +452,11 @@
   }
 
   public static final class JoinSpec.Builder {
+    ctor public JoinSpec.Builder(androidx.appsearch.app.JoinSpec);
     ctor public JoinSpec.Builder(String);
     method public androidx.appsearch.app.JoinSpec build();
     method public androidx.appsearch.app.JoinSpec.Builder setAggregationScoringStrategy(int);
+    method public androidx.appsearch.app.JoinSpec.Builder setChildPropertyExpression(String);
     method public androidx.appsearch.app.JoinSpec.Builder setMaxJoinedResultCount(int);
     method public androidx.appsearch.app.JoinSpec.Builder setNestedSearch(String, androidx.appsearch.app.SearchSpec);
   }
@@ -663,6 +674,7 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    ctor public SearchSpec.Builder(androidx.appsearch.app.SearchSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
@@ -686,6 +698,18 @@
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSpec build();
+    method public androidx.appsearch.app.SearchSpec.Builder clearEmbeddingParameters();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterNamespaces();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterPackageNames();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterProperties();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterSchemas();
+    method public androidx.appsearch.app.SearchSpec.Builder clearInformationalRankingExpressions();
+    method public androidx.appsearch.app.SearchSpec.Builder clearJoinSpec();
+    method public androidx.appsearch.app.SearchSpec.Builder clearProjections();
+    method public androidx.appsearch.app.SearchSpec.Builder clearPropertyWeights();
+    method public androidx.appsearch.app.SearchSpec.Builder clearResultGrouping();
+    method public androidx.appsearch.app.SearchSpec.Builder clearSearchSourceLogTag();
+    method public androidx.appsearch.app.SearchSpec.Builder clearSearchStringParameters();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
@@ -771,6 +795,7 @@
 
   public static final class SetSchemaRequest.Builder {
     ctor public SetSchemaRequest.Builder();
+    ctor public SetSchemaRequest.Builder(androidx.appsearch.app.SetSchemaRequest);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClassVisibleToConfig(Class<? extends java.lang.Object!>, androidx.appsearch.app.SchemaVisibilityConfig) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
@@ -781,9 +806,11 @@
     method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
     method public androidx.appsearch.app.SetSchemaRequest build();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearDocumentClassVisibleToConfigs(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+    method public androidx.appsearch.app.SetSchemaRequest.Builder clearMigrators();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemaTypeVisibleToConfigs(String);
+    method public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemas();
     method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassDisplayedBySystem(Class<? extends java.lang.Object!>, boolean) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassVisibilityForPackage(Class<? extends java.lang.Object!>, boolean, androidx.appsearch.app.PackageIdentifier) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder setForceOverride(boolean);
@@ -847,6 +874,10 @@
 
 package androidx.appsearch.ast {
 
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface FunctionNode extends androidx.appsearch.ast.Node {
+    method public String getFunctionName();
+  }
+
   @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
     ctor public NegationNode(androidx.appsearch.ast.Node);
     method public androidx.appsearch.ast.Node getChild();
@@ -870,6 +901,28 @@
 
 }
 
+package androidx.appsearch.ast.operators {
+
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AndNode implements androidx.appsearch.ast.Node {
+    ctor public AndNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+    ctor public AndNode(java.util.List<androidx.appsearch.ast.Node!>);
+    method public void addChild(androidx.appsearch.ast.Node);
+    method public void removeChild(int);
+    method public void setChild(int, androidx.appsearch.ast.Node);
+    method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+  }
+
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OrNode implements androidx.appsearch.ast.Node {
+    ctor public OrNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+    ctor public OrNode(java.util.List<androidx.appsearch.ast.Node!>);
+    method public void addChild(androidx.appsearch.ast.Node);
+    method public void removeChild(int);
+    method public void setChild(int, androidx.appsearch.ast.Node);
+    method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+  }
+
+}
+
 package androidx.appsearch.exceptions {
 
   public class AppSearchException extends java.lang.Exception {
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 6ebee26..8ec1c3d 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -94,6 +94,7 @@
 
   public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
     ctor public AppSearchBatchResult.Builder();
+    ctor public AppSearchBatchResult.Builder(androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!>);
     method public androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!> build();
     method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setFailure(KeyType, int, String?);
     method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setResult(KeyType, androidx.appsearch.app.AppSearchResult<ValueType!>);
@@ -146,11 +147,15 @@
   }
 
   public static final class AppSearchSchema.Builder {
+    ctor public AppSearchSchema.Builder(androidx.appsearch.app.AppSearchSchema);
     ctor public AppSearchSchema.Builder(String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
     method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
     method public androidx.appsearch.app.AppSearchSchema build();
+    method public androidx.appsearch.app.AppSearchSchema.Builder clearParentTypes();
+    method public androidx.appsearch.app.AppSearchSchema.Builder clearProperties();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) public androidx.appsearch.app.AppSearchSchema.Builder setDescription(String);
+    method public androidx.appsearch.app.AppSearchSchema.Builder setSchemaType(String);
   }
 
   public static final class AppSearchSchema.BytesPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
@@ -406,9 +411,13 @@
 
   public static final class GetSchemaResponse.Builder {
     ctor public GetSchemaResponse.Builder();
+    ctor public GetSchemaResponse.Builder(androidx.appsearch.app.GetSchemaResponse);
     method public androidx.appsearch.app.GetSchemaResponse.Builder addSchema(androidx.appsearch.app.AppSearchSchema);
     method public androidx.appsearch.app.GetSchemaResponse.Builder addSchemaTypeNotDisplayedBySystem(String);
     method public androidx.appsearch.app.GetSchemaResponse build();
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibilityConfig(String);
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibilityConfigs();
+    method public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemas();
     method public androidx.appsearch.app.GetSchemaResponse.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier);
     method public androidx.appsearch.app.GetSchemaResponse.Builder setRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.util.Set<java.lang.Integer!>!>);
     method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToConfigs(String, java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>);
@@ -443,9 +452,11 @@
   }
 
   public static final class JoinSpec.Builder {
+    ctor public JoinSpec.Builder(androidx.appsearch.app.JoinSpec);
     ctor public JoinSpec.Builder(String);
     method public androidx.appsearch.app.JoinSpec build();
     method public androidx.appsearch.app.JoinSpec.Builder setAggregationScoringStrategy(int);
+    method public androidx.appsearch.app.JoinSpec.Builder setChildPropertyExpression(String);
     method public androidx.appsearch.app.JoinSpec.Builder setMaxJoinedResultCount(int);
     method public androidx.appsearch.app.JoinSpec.Builder setNestedSearch(String, androidx.appsearch.app.SearchSpec);
   }
@@ -663,6 +674,7 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    ctor public SearchSpec.Builder(androidx.appsearch.app.SearchSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
@@ -686,6 +698,18 @@
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSpec build();
+    method public androidx.appsearch.app.SearchSpec.Builder clearEmbeddingParameters();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterNamespaces();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterPackageNames();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterProperties();
+    method public androidx.appsearch.app.SearchSpec.Builder clearFilterSchemas();
+    method public androidx.appsearch.app.SearchSpec.Builder clearInformationalRankingExpressions();
+    method public androidx.appsearch.app.SearchSpec.Builder clearJoinSpec();
+    method public androidx.appsearch.app.SearchSpec.Builder clearProjections();
+    method public androidx.appsearch.app.SearchSpec.Builder clearPropertyWeights();
+    method public androidx.appsearch.app.SearchSpec.Builder clearResultGrouping();
+    method public androidx.appsearch.app.SearchSpec.Builder clearSearchSourceLogTag();
+    method public androidx.appsearch.app.SearchSpec.Builder clearSearchStringParameters();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
@@ -771,6 +795,7 @@
 
   public static final class SetSchemaRequest.Builder {
     ctor public SetSchemaRequest.Builder();
+    ctor public SetSchemaRequest.Builder(androidx.appsearch.app.SetSchemaRequest);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClassVisibleToConfig(Class<? extends java.lang.Object!>, androidx.appsearch.app.SchemaVisibilityConfig) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
@@ -781,9 +806,11 @@
     method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
     method public androidx.appsearch.app.SetSchemaRequest build();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearDocumentClassVisibleToConfigs(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+    method public androidx.appsearch.app.SetSchemaRequest.Builder clearMigrators();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemaTypeVisibleToConfigs(String);
+    method public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemas();
     method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassDisplayedBySystem(Class<? extends java.lang.Object!>, boolean) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassVisibilityForPackage(Class<? extends java.lang.Object!>, boolean, androidx.appsearch.app.PackageIdentifier) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SetSchemaRequest.Builder setForceOverride(boolean);
@@ -847,6 +874,10 @@
 
 package androidx.appsearch.ast {
 
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface FunctionNode extends androidx.appsearch.ast.Node {
+    method public String getFunctionName();
+  }
+
   @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
     ctor public NegationNode(androidx.appsearch.ast.Node);
     method public androidx.appsearch.ast.Node getChild();
@@ -870,6 +901,28 @@
 
 }
 
+package androidx.appsearch.ast.operators {
+
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AndNode implements androidx.appsearch.ast.Node {
+    ctor public AndNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+    ctor public AndNode(java.util.List<androidx.appsearch.ast.Node!>);
+    method public void addChild(androidx.appsearch.ast.Node);
+    method public void removeChild(int);
+    method public void setChild(int, androidx.appsearch.ast.Node);
+    method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+  }
+
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OrNode implements androidx.appsearch.ast.Node {
+    ctor public OrNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+    ctor public OrNode(java.util.List<androidx.appsearch.ast.Node!>);
+    method public void addChild(androidx.appsearch.ast.Node);
+    method public void removeChild(int);
+    method public void setChild(int, androidx.appsearch.ast.Node);
+    method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+  }
+
+}
+
 package androidx.appsearch.exceptions {
 
   public class AppSearchException extends java.lang.Exception {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchBatchResultCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchBatchResultCtsTest.java
index 0f8a50e..0852d6f 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchBatchResultCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchBatchResultCtsTest.java
@@ -20,10 +20,18 @@
 
 import androidx.appsearch.app.AppSearchBatchResult;
 import androidx.appsearch.app.AppSearchResult;
+import androidx.appsearch.flags.CheckFlagsRule;
+import androidx.appsearch.flags.DeviceFlagsValueProvider;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.flags.RequiresFlagsEnabled;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 public class AppSearchBatchResultCtsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void testIsSuccess_true() {
         AppSearchBatchResult<String, Integer> result =
@@ -116,4 +124,23 @@
                 AppSearchResult.newFailedResult(
                         AppSearchResult.RESULT_INVALID_ARGUMENT, "message3"));
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testAppSearchBatchResultBuilder_copyConstructor() {
+        AppSearchBatchResult<String, Integer> result =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("keySuccess1", 1)
+                        .setSuccess("keySuccess2", 2)
+                        .setFailure(
+                                "keyFailure1", AppSearchResult.RESULT_UNKNOWN_ERROR, "message1")
+                        .setFailure(
+                                "keyFailure2", AppSearchResult.RESULT_INTERNAL_ERROR, "message2")
+                        .build();
+        AppSearchBatchResult<String, Integer> resultCopy = new AppSearchBatchResult.Builder<>(
+                result).build();
+        assertThat(resultCopy.getAll()).isEqualTo(result.getAll());
+        assertThat(resultCopy.getSuccesses()).isEqualTo(result.getSuccesses());
+        assertThat(resultCopy.getFailures()).isEqualTo(result.getFailures());
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
index 0e4952c..7d5e714 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
@@ -971,4 +971,56 @@
                 new AppSearchSchema.EmbeddingPropertyConfig.Builder("titleEmbedding")
                         .setIndexingType(-1).build());
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testAppSearchSchemaBuilder_copyConstructor() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("Email")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                )
+                .addParentType("Document").build();
+        AppSearchSchema schemaCopy = new AppSearchSchema.Builder(schema).build();
+        assertThat(schemaCopy.getSchemaType()).isEqualTo(schema.getSchemaType());
+        assertThat(schemaCopy.getProperties()).isEqualTo(schema.getProperties());
+        assertThat(schemaCopy.getParentTypes()).isEqualTo(schema.getParentTypes());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testAppSearchSchemaBuilder_setSchemaType() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("Email")
+                .setSchemaType("Email2").build();
+        assertThat(schema.getSchemaType()).isEqualTo("Email2");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testAppSearchSchemaBuilder_clearProperties() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("Email")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                )
+                .clearProperties().build();
+        assertThat(schema.getSchemaType()).isEqualTo("Email");
+        assertThat(schema.getProperties()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testAppSearchSchemaBuilder_clearParentTypes() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("Email")
+                .addParentType("Document").addParentType("Thing").clearParentTypes()
+                .build();
+        assertThat(schema.getSchemaType()).isEqualTo("Email");
+        assertThat(schema.getParentTypes()).isEmpty();
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GetSchemaResponseCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GetSchemaResponseCtsTest.java
index 4d5a4f9..c4a7722 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GetSchemaResponseCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GetSchemaResponseCtsTest.java
@@ -25,15 +25,23 @@
 import androidx.appsearch.app.PackageIdentifier;
 import androidx.appsearch.app.SchemaVisibilityConfig;
 import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.flags.CheckFlagsRule;
+import androidx.appsearch.flags.DeviceFlagsValueProvider;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.flags.RequiresFlagsEnabled;
 
 import com.google.common.collect.ImmutableSet;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Map;
 
 public class GetSchemaResponseCtsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void testRebuild() {
         byte[] sha256cert1 = new byte[32];
@@ -158,7 +166,6 @@
                                 .READ_ASSISTANT_APP_SEARCH_DATA)));
     }
 
-
     @Test
     public void setVisibilityConfig() {
         SchemaVisibilityConfig visibilityConfig1 = new SchemaVisibilityConfig.Builder()
@@ -342,4 +349,233 @@
                 + " this backend/Android API level combination.");
     }
     // @exportToFramework:endStrip()
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testGetSchemaResponseBuilder_copyConstructor() {
+        byte[] sha256cert1 = new byte[32];
+        byte[] sha256cert2 = new byte[32];
+        Arrays.fill(sha256cert1, (byte) 1);
+        Arrays.fill(sha256cert2, (byte) 2);
+        PackageIdentifier packageIdentifier1 = new PackageIdentifier("Email", sha256cert1);
+        PackageIdentifier packageIdentifier2 = new PackageIdentifier("Email", sha256cert2);
+        SchemaVisibilityConfig schemaVisibilityConfig =
+                new SchemaVisibilityConfig.Builder().build();
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("Email1")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("Email2")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        GetSchemaResponse response =
+                new GetSchemaResponse.Builder().setVersion(42).addSchema(schema1).addSchema(schema2)
+                        .addSchemaTypeNotDisplayedBySystem("Email1")
+                        .addSchemaTypeNotDisplayedBySystem("Email2")
+                        .setSchemaTypeVisibleToPackages("Email1",
+                                ImmutableSet.of(packageIdentifier1))
+                        .setSchemaTypeVisibleToPackages("Email2",
+                                ImmutableSet.of(packageIdentifier2))
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email1",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_SMS,
+                                                SetSchemaRequest.READ_CALENDAR),
+                                        ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
+                        )
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email2",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_CONTACTS,
+                                                SetSchemaRequest.READ_EXTERNAL_STORAGE),
+                                        ImmutableSet.of(SetSchemaRequest
+                                                .READ_ASSISTANT_APP_SEARCH_DATA)))
+                        .setPubliclyVisibleSchema("Email1", packageIdentifier1)
+                        .setPubliclyVisibleSchema("Email2", packageIdentifier2)
+                        .setSchemaTypeVisibleToConfigs("Email1",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .setSchemaTypeVisibleToConfigs("Email2",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .build();
+        GetSchemaResponse responseCopy = new GetSchemaResponse.Builder(response).build();
+        assertThat(responseCopy.getVersion()).isEqualTo(response.getVersion());
+        assertThat(responseCopy.getSchemas()).isEqualTo(response.getSchemas());
+        assertThat(responseCopy.getPubliclyVisibleSchemas()).isEqualTo(
+                response.getPubliclyVisibleSchemas());
+        assertThat(responseCopy.getRequiredPermissionsForSchemaTypeVisibility()).isEqualTo(
+                response.getRequiredPermissionsForSchemaTypeVisibility());
+        assertThat(responseCopy.getSchemaTypesVisibleToConfigs()).isEqualTo(
+                response.getSchemaTypesVisibleToConfigs());
+        assertThat(responseCopy.getSchemaTypesVisibleToPackages()).isEqualTo(
+                response.getSchemaTypesVisibleToPackages());
+        assertThat(responseCopy.getSchemaTypesNotDisplayedBySystem()).isEqualTo(
+                response.getSchemaTypesNotDisplayedBySystem());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testGetSchemaResponseBuilder_clearSchemas() {
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("Email1")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("Email2")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        GetSchemaResponse response = new GetSchemaResponse.Builder()
+                .addSchema(schema1)
+                .addSchema(schema2)
+                .clearSchemas()
+                .build();
+        assertThat(response.getSchemas()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testGetSchemaResponseBuilder_clearSchemaTypeVisibilityConfig() {
+        byte[] sha256cert1 = new byte[32];
+        byte[] sha256cert2 = new byte[32];
+        Arrays.fill(sha256cert1, (byte) 1);
+        Arrays.fill(sha256cert2, (byte) 2);
+        PackageIdentifier packageIdentifier1 = new PackageIdentifier("Email", sha256cert1);
+        PackageIdentifier packageIdentifier2 = new PackageIdentifier("Email", sha256cert2);
+        SchemaVisibilityConfig schemaVisibilityConfig =
+                new SchemaVisibilityConfig.Builder().build();
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("Email1")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("Email2")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        GetSchemaResponse response =
+                new GetSchemaResponse.Builder().setVersion(42).addSchema(schema1).addSchema(schema2)
+                        .addSchemaTypeNotDisplayedBySystem("Email1")
+                        .addSchemaTypeNotDisplayedBySystem("Email2")
+                        .setSchemaTypeVisibleToPackages("Email1",
+                                ImmutableSet.of(packageIdentifier1))
+                        .setSchemaTypeVisibleToPackages("Email2",
+                                ImmutableSet.of(packageIdentifier2))
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email1",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_SMS,
+                                                SetSchemaRequest.READ_CALENDAR),
+                                        ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
+                        )
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email2",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_CONTACTS,
+                                                SetSchemaRequest.READ_EXTERNAL_STORAGE),
+                                        ImmutableSet.of(SetSchemaRequest
+                                                .READ_ASSISTANT_APP_SEARCH_DATA)))
+                        .setPubliclyVisibleSchema("Email1", packageIdentifier1)
+                        .setPubliclyVisibleSchema("Email2", packageIdentifier2)
+                        .setSchemaTypeVisibleToConfigs("Email1",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .setSchemaTypeVisibleToConfigs("Email2",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .clearSchemaTypeVisibilityConfig("Email1")
+                        .build();
+        assertThat(response.getSchemaTypesNotDisplayedBySystem()).containsExactly("Email2");
+        assertThat(response.getSchemaTypesVisibleToPackages()).containsExactly("Email2",
+                ImmutableSet.of(packageIdentifier2));
+        assertThat(response.getRequiredPermissionsForSchemaTypeVisibility()).containsExactly(
+                "Email2", ImmutableSet.of(
+                        ImmutableSet.of(SetSchemaRequest.READ_CONTACTS,
+                                SetSchemaRequest.READ_EXTERNAL_STORAGE),
+                        ImmutableSet.of(SetSchemaRequest
+                                .READ_ASSISTANT_APP_SEARCH_DATA)));
+        assertThat(response.getPubliclyVisibleSchemas()).containsExactly("Email2",
+                packageIdentifier2);
+        assertThat(response.getSchemaTypesVisibleToConfigs()).containsExactly("Email2",
+                ImmutableSet.of(schemaVisibilityConfig));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testGetSchemaResponseBuilder_clearSchemaTypeVisibilityConfigs() {
+        byte[] sha256cert1 = new byte[32];
+        byte[] sha256cert2 = new byte[32];
+        Arrays.fill(sha256cert1, (byte) 1);
+        Arrays.fill(sha256cert2, (byte) 2);
+        PackageIdentifier packageIdentifier1 = new PackageIdentifier("Email", sha256cert1);
+        PackageIdentifier packageIdentifier2 = new PackageIdentifier("Email", sha256cert2);
+        SchemaVisibilityConfig schemaVisibilityConfig =
+                new SchemaVisibilityConfig.Builder().build();
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("Email1")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("Email2")
+                .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        GetSchemaResponse response =
+                new GetSchemaResponse.Builder().setVersion(42).addSchema(schema1).addSchema(schema2)
+                        .addSchemaTypeNotDisplayedBySystem("Email1")
+                        .addSchemaTypeNotDisplayedBySystem("Email2")
+                        .setSchemaTypeVisibleToPackages("Email1",
+                                ImmutableSet.of(packageIdentifier1))
+                        .setSchemaTypeVisibleToPackages("Email2",
+                                ImmutableSet.of(packageIdentifier2))
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email1",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_SMS,
+                                                SetSchemaRequest.READ_CALENDAR),
+                                        ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
+                        )
+                        .setRequiredPermissionsForSchemaTypeVisibility("Email2",
+                                ImmutableSet.of(
+                                        ImmutableSet.of(SetSchemaRequest.READ_CONTACTS,
+                                                SetSchemaRequest.READ_EXTERNAL_STORAGE),
+                                        ImmutableSet.of(SetSchemaRequest
+                                                .READ_ASSISTANT_APP_SEARCH_DATA)))
+                        .setPubliclyVisibleSchema("Email1", packageIdentifier1)
+                        .setPubliclyVisibleSchema("Email2", packageIdentifier2)
+                        .setSchemaTypeVisibleToConfigs("Email1",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .setSchemaTypeVisibleToConfigs("Email2",
+                                ImmutableSet.of(schemaVisibilityConfig))
+                        .clearSchemaTypeVisibilityConfig("Email1")
+                        .clearSchemaTypeVisibilityConfigs()
+                        .build();
+        assertThat(response.getSchemaTypesNotDisplayedBySystem()).isEmpty();
+        assertThat(response.getSchemaTypesVisibleToPackages()).isEmpty();
+        assertThat(response.getRequiredPermissionsForSchemaTypeVisibility()).isEmpty();
+        assertThat(response.getPubliclyVisibleSchemas()).isEmpty();
+        assertThat(response.getSchemaTypesVisibleToConfigs()).isEmpty();
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
index a58b892..a307a22 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
@@ -20,10 +20,17 @@
 
 import androidx.appsearch.app.JoinSpec;
 import androidx.appsearch.app.SearchSpec;
+import androidx.appsearch.flags.CheckFlagsRule;
+import androidx.appsearch.flags.DeviceFlagsValueProvider;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.flags.RequiresFlagsEnabled;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 public class JoinSpecCtsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
     public void testBuild() {
@@ -64,4 +71,35 @@
         assertThat(joinSpec.getNestedSearchSpec().getFilterNamespaces())
                 .isEqualTo(empty.getFilterNamespaces());
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testJoinSpecBuilder_copyConstructor() {
+        SearchSpec originalNestedSearchSpec = new SearchSpec.Builder()
+                .addFilterSchemas("Action", "CallAction")
+                .build();
+        JoinSpec joinSpec = new JoinSpec.Builder("entityId")
+                .setMaxJoinedResultCount(5)
+                .setAggregationScoringStrategy(JoinSpec.AGGREGATION_SCORING_RESULT_COUNT)
+                .setNestedSearch("joe", originalNestedSearchSpec)
+                .build();
+        JoinSpec joinSpecCopy = new JoinSpec.Builder(joinSpec).build();
+        assertThat(joinSpecCopy.getNestedQuery()).isEqualTo(joinSpec.getNestedQuery());
+        assertThat(joinSpecCopy.getNestedSearchSpec()).isEqualTo(joinSpec.getNestedSearchSpec());
+        assertThat(joinSpecCopy.getChildPropertyExpression()).isEqualTo(
+                joinSpec.getChildPropertyExpression());
+        assertThat(joinSpecCopy.getMaxJoinedResultCount()).isEqualTo(
+                joinSpec.getMaxJoinedResultCount());
+        assertThat(joinSpecCopy.getAggregationScoringStrategy()).isEqualTo(
+                joinSpec.getAggregationScoringStrategy());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testJoinSpecBuilder_setChildPropertyExpression() {
+        JoinSpec joinSpec = new JoinSpec.Builder("entityId")
+                .setChildPropertyExpression("entityId2")
+                .build();
+        assertThat(joinSpec.getChildPropertyExpression()).isEqualTo("entityId2");
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
index 8dd3688..7a899c4 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
@@ -828,4 +828,194 @@
         assertThat(rebuild.getSearchStringParameters())
                 .containsExactly("A", "b", "C", "d").inOrder();
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testSearchSpecBuilder_copyConstructor() {
+        List<String> expectedPropertyPaths1 = ImmutableList.of("path1", "path2");
+        List<String> expectedPropertyPaths2 = ImmutableList.of("path3", "path4");
+        Map<String, Double> expectedPropertyWeights = ImmutableMap.of("property1", 1.0,
+                "property2", 2.0);
+        Map<PropertyPath, Double> expectedPropertyWeightPaths =
+                ImmutableMap.of(new PropertyPath("property1.nested"), 1.0);
+
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                .addFilterNamespaces("namespace1", "namespace2")
+                .addFilterNamespaces(ImmutableList.of("namespace3"))
+                .addFilterSchemas("schemaTypes1", "schemaTypes2")
+                .addFilterSchemas(ImmutableList.of("schemaTypes3"))
+                .addFilterPackageNames("package1", "package2")
+                .addFilterPackageNames(ImmutableList.of("package3"))
+                .setSnippetCount(5)
+                .setSnippetCountPerProperty(10)
+                .setMaxSnippetSize(15)
+                .setResultCountPerPage(42)
+                .setOrder(SearchSpec.ORDER_ASCENDING)
+                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
+                .setResultGrouping(SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                        | SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*limit=*/ 37)
+                .addProjection("schemaTypes1", expectedPropertyPaths1)
+                .addProjection("schemaTypes2", expectedPropertyPaths2)
+                .setPropertyWeights("schemaTypes1", expectedPropertyWeights)
+                .setPropertyWeightPaths("schemaTypes2", expectedPropertyWeightPaths)
+                .setNumericSearchEnabled(true)
+                .setVerbatimSearchEnabled(true)
+                .setListFilterQueryLanguageEnabled(true)
+                .build();
+        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
+        assertThat(searchSpecCopy.getTermMatch()).isEqualTo(searchSpec.getTermMatch());
+        assertThat(searchSpecCopy.getFilterNamespaces()).isEqualTo(
+                searchSpec.getFilterNamespaces());
+        assertThat(searchSpecCopy.getFilterSchemas()).isEqualTo(searchSpec.getFilterSchemas());
+        assertThat(searchSpecCopy.getFilterPackageNames()).isEqualTo(
+                searchSpec.getFilterPackageNames());
+        assertThat(searchSpecCopy.getSnippetCount()).isEqualTo(searchSpec.getSnippetCount());
+        assertThat(searchSpecCopy.getSnippetCountPerProperty()).isEqualTo(
+                searchSpec.getSnippetCountPerProperty());
+        assertThat(searchSpecCopy.getMaxSnippetSize()).isEqualTo(searchSpec.getMaxSnippetSize());
+        assertThat(searchSpecCopy.getResultCountPerPage()).isEqualTo(
+                searchSpec.getResultCountPerPage());
+        assertThat(searchSpecCopy.getOrder()).isEqualTo(searchSpec.getOrder());
+        assertThat(searchSpecCopy.getRankingStrategy()).isEqualTo(searchSpec.getRankingStrategy());
+        assertThat(searchSpecCopy.getResultGroupingTypeFlags()).isEqualTo(
+                searchSpec.getResultGroupingTypeFlags());
+        assertThat(searchSpecCopy.getProjections()).isEqualTo(searchSpec.getProjections());
+        assertThat(searchSpecCopy.getResultGroupingLimit()).isEqualTo(
+                searchSpec.getResultGroupingLimit());
+        assertThat(searchSpecCopy.getPropertyWeights()).isEqualTo(searchSpec.getPropertyWeights());
+        assertThat(searchSpecCopy.getPropertyWeightPaths()).isEqualTo(
+                searchSpec.getPropertyWeightPaths());
+        assertThat(searchSpecCopy.isNumericSearchEnabled()).isEqualTo(
+                searchSpec.isNumericSearchEnabled());
+        assertThat(searchSpecCopy.isVerbatimSearchEnabled()).isEqualTo(
+                searchSpec.isVerbatimSearchEnabled());
+        assertThat(searchSpecCopy.isListFilterQueryLanguageEnabled()).isEqualTo(
+                searchSpec.isListFilterQueryLanguageEnabled());
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES})
+    public void testSearchSpecBuilder_clearBuilderParameters() {
+        JoinSpec joinSpec = new JoinSpec.Builder("query").build();
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .addFilterNamespaces("namespace1", "namespace2")
+                .addFilterSchemas("schemaTypes1", "schemaTypes2")
+                .addFilterPackageNames("package1", "package2")
+                .addFilterProperties("schemaTypes1", ImmutableList.of("path1", "path2"))
+                .addFilterProperties("schemaTypes2", ImmutableList.of("path3", "path4"))
+                .addProjection("schemaTypes1", ImmutableList.of("path1", "path2"))
+                .addProjection("schemaTypes2", ImmutableList.of("path3", "path4"))
+                .setPropertyWeights("schemaTypes1", ImmutableMap.of("property1", 1.0))
+                .setPropertyWeights("schemaTypes2", ImmutableMap.of("property2", 2.0))
+                .setResultGrouping(SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                        | SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*limit=*/ 37)
+                .setJoinSpec(joinSpec)
+                .clearFilterNamespaces()
+                .clearFilterPackageNames()
+                .clearFilterProperties()
+                .clearFilterSchemas()
+                .clearJoinSpec()
+                .clearProjections()
+                .clearPropertyWeights()
+                .clearResultGrouping()
+                .build();
+        assertThat(searchSpec.getFilterNamespaces()).isEmpty();
+        assertThat(searchSpec.getFilterPackageNames()).isEmpty();
+        assertThat(searchSpec.getFilterProperties()).isEmpty();
+        assertThat(searchSpec.getFilterSchemas()).isEmpty();
+        assertThat(searchSpec.getProjections()).isEmpty();
+        assertThat(searchSpec.getPropertyWeights()).isEmpty();
+        assertThat(searchSpec.getResultGroupingLimit()).isEqualTo(0);
+        assertThat(searchSpec.getResultGroupingTypeFlags()).isEqualTo(0);
+        assertThat(searchSpec.getJoinSpec()).isNull();
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG})
+    public void testSearchSpecBuilder_copyConstructor_embeddingParameters() {
+        EmbeddingVector embedding1 = new EmbeddingVector(
+                new float[]{1.1f, 2.2f, 3.3f}, "my_model_v1");
+        EmbeddingVector embedding2 = new EmbeddingVector(
+                new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
+        SearchSpec searchSpec = new SearchSpec.Builder().addEmbeddingParameters(embedding1,
+                embedding2).build();
+        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
+        assertThat(searchSpecCopy.getEmbeddingParameters()).isEqualTo(
+                searchSpec.getEmbeddingParameters());
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG})
+    public void testSearchSpecBuilder_copyConstructor_clearEmbeddingParameters() {
+        EmbeddingVector embedding1 = new EmbeddingVector(
+                new float[]{1.1f, 2.2f, 3.3f}, "my_model_v1");
+        EmbeddingVector embedding2 = new EmbeddingVector(
+                new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
+        SearchSpec searchSpec = new SearchSpec.Builder().addEmbeddingParameters(embedding1,
+                embedding2).clearEmbeddingParameters().build();
+        assertThat(searchSpec.getEmbeddingParameters()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_INFORMATIONAL_RANKING_EXPRESSIONS})
+    public void testSearchSpecBuilder_copyConstructor_informationalRankingExpressions() {
+        SearchSpec searchSpec = new SearchSpec.Builder().addInformationalRankingExpressions("info1",
+                "info2").build();
+        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
+        assertThat(searchSpecCopy.getInformationalRankingExpressions()).isEqualTo(
+                searchSpec.getInformationalRankingExpressions());
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_INFORMATIONAL_RANKING_EXPRESSIONS})
+    public void testSearchSpecBuilder_clearInformationalRankingExpressions() {
+        SearchSpec searchSpec = new SearchSpec.Builder().addInformationalRankingExpressions("info1",
+                "info2").clearInformationalRankingExpressions().build();
+        assertThat(searchSpec.getInformationalRankingExpressions()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG})
+    public void testSearchSpecBuilder_copyConstructor_searchSourceLogTag() {
+        SearchSpec searchSpec = new SearchSpec.Builder().setSearchSourceLogTag("source").build();
+        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
+        assertThat(searchSpecCopy.getSearchSourceLogTag()).isEqualTo(
+                searchSpec.getSearchSourceLogTag());
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG})
+    public void testSearchSpecBuilder_clearSearchSourceLogTag() {
+        SearchSpec searchSpec = new SearchSpec.Builder().setSearchSourceLogTag(
+                "source").clearSearchSourceLogTag().build();
+        assertThat(searchSpec.getSearchSourceLogTag()).isNull();
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS})
+    public void testSearchSpecBuilder_copyConstructor_searchStringParameters() {
+        SearchSpec searchSpec = new SearchSpec.Builder().addSearchStringParameters("param1",
+                "param2").build();
+        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
+        assertThat(searchSpecCopy.getSearchStringParameters()).isEqualTo(
+                searchSpec.getSearchStringParameters());
+    }
+
+    @Test
+    @RequiresFlagsEnabled({Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS,
+            Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS})
+    public void testSearchSpecBuilder_clearSearchStringParameters() {
+        SearchSpec searchSpec = new SearchSpec.Builder().addSearchStringParameters("param1",
+                "param2").clearSearchStringParameters().build();
+        assertThat(searchSpec.getSearchStringParameters()).isEmpty();
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
index 1876b87..315b037 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
@@ -33,11 +33,16 @@
 import androidx.appsearch.app.SchemaVisibilityConfig;
 import androidx.appsearch.app.SetSchemaRequest;
 import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.flags.CheckFlagsRule;
+import androidx.appsearch.flags.DeviceFlagsValueProvider;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.flags.RequiresFlagsEnabled;
 import androidx.appsearch.testutil.AppSearchEmail;
 import androidx.collection.ArrayMap;
 
 import com.google.common.collect.ImmutableSet;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -49,6 +54,9 @@
 import java.util.Set;
 
 public class SetSchemaRequestCtsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void testBuildSetSchemaRequest() {
         AppSearchSchema.StringPropertyConfig prop1 =
@@ -105,66 +113,9 @@
         AppSearchSchema schema3 =
                 new AppSearchSchema.Builder("type3").addProperty(requiredProp).build();
 
-        Migrator expectedMigrator1 = new Migrator() {
-            @Override
-            public boolean shouldMigrate(int currentVersion, int finalVersion) {
-                return true;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onUpgrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onDowngrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-        };
-        Migrator expectedMigrator2 = new Migrator() {
-            @Override
-            public boolean shouldMigrate(int currentVersion, int finalVersion) {
-                return true;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onUpgrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onDowngrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-        };
-        Migrator expectedMigrator3 = new Migrator() {
-            @Override
-            public boolean shouldMigrate(int currentVersion, int finalVersion) {
-                return true;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onUpgrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-
-            @NonNull
-            @Override
-            public GenericDocument onDowngrade(int currentVersion, int finalVersion,
-                    @NonNull GenericDocument document) {
-                return document;
-            }
-        };
+        Migrator expectedMigrator1 = new NoOpMigrator();
+        Migrator expectedMigrator2 = new NoOpMigrator();
+        Migrator expectedMigrator3 = new NoOpMigrator();
         Map<String, Migrator> migratorMap = new ArrayMap<>();
         migratorMap.put("type1", expectedMigrator1);
         migratorMap.put("type2", expectedMigrator2);
@@ -1144,6 +1095,164 @@
                 .isEqualTo(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testSetSchemaRequestBuilder_copyConstructor() {
+        AppSearchSchema.StringPropertyConfig prop1 =
+                new AppSearchSchema.StringPropertyConfig.Builder("prop1")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build();
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("type1").addProperty(prop1).build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("type2").addProperty(prop1).build();
+        AppSearchSchema schema3 =
+                new AppSearchSchema.Builder("type3").addProperty(prop1).build();
+        AppSearchSchema schema4 =
+                new AppSearchSchema.Builder("type4").addProperty(prop1).build();
+
+        PackageIdentifier packageIdentifier =
+                new PackageIdentifier("com.package.foo", new byte[]{100});
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(schema1, schema2)
+                .addSchemas(Arrays.asList(schema3, schema4))
+                .setSchemaTypeDisplayedBySystem("type2", /*displayed=*/ false)
+                .setSchemaTypeVisibilityForPackage("type1", /*visible=*/ true,
+                        packageIdentifier)
+                .addRequiredPermissionsForSchemaTypeVisibility("type3",
+                        Collections.singleton(SetSchemaRequest.READ_CONTACTS))
+                .setPubliclyVisibleSchema("type4", packageIdentifier)
+                .addSchemaTypeVisibleToConfig("type1", new SchemaVisibilityConfig.Builder().build())
+                .setMigrator("type2", new NoOpMigrator())
+                .setForceOverride(true)
+                .setVersion(142857)
+                .build();
+
+        SetSchemaRequest requestCopy = new SetSchemaRequest.Builder(request).build();
+        assertThat(requestCopy.getSchemas()).isEqualTo(request.getSchemas());
+        assertThat(requestCopy.getSchemasNotDisplayedBySystem()).isEqualTo(
+                request.getSchemasNotDisplayedBySystem());
+        assertThat(requestCopy.getSchemasVisibleToPackages()).isEqualTo(
+                request.getSchemasVisibleToPackages());
+        assertThat(requestCopy.getRequiredPermissionsForSchemaTypeVisibility()).isEqualTo(
+                request.getRequiredPermissionsForSchemaTypeVisibility());
+        assertThat(requestCopy.getPubliclyVisibleSchemas()).isEqualTo(
+                request.getPubliclyVisibleSchemas());
+        assertThat(requestCopy.getSchemasVisibleToConfigs()).isEqualTo(
+                request.getSchemasVisibleToConfigs());
+        assertThat(requestCopy.getMigrators()).isEqualTo(request.getMigrators());
+        assertThat(requestCopy.getVersion()).isEqualTo(request.getVersion());
+        assertThat(requestCopy.isForceOverride()).isEqualTo(request.isForceOverride());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testSetSchemaRequestBuilder_copyConstructor_usesDeepCopies() {
+        // Previously, the copy constructor did not make deep copies of all fields, so modifying the
+        // builder could affect the request that the builder was created from
+        AppSearchSchema.StringPropertyConfig prop1 =
+                new AppSearchSchema.StringPropertyConfig.Builder("prop1")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build();
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("type1").addProperty(prop1).build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("type2").addProperty(prop1).build();
+        AppSearchSchema schema3 =
+                new AppSearchSchema.Builder("type3").addProperty(prop1).build();
+        AppSearchSchema schema4 =
+                new AppSearchSchema.Builder("type4").addProperty(prop1).build();
+
+        PackageIdentifier packageIdentifier =
+                new PackageIdentifier("com.package.foo", new byte[]{100});
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(schema1, schema2, schema3, schema4)
+                .setSchemaTypeVisibilityForPackage("type1", /*visible=*/ true,
+                        packageIdentifier)
+                .addRequiredPermissionsForSchemaTypeVisibility("type3",
+                        Collections.singleton(SetSchemaRequest.READ_CONTACTS))
+                .addSchemaTypeVisibleToConfig("type1", new SchemaVisibilityConfig.Builder().build())
+                .build();
+
+        PackageIdentifier otherPackageIdentifier =
+                new PackageIdentifier("com.package.bar", new byte[]{100});
+
+        // Create a copy builder and modify the visibility settings
+        SetSchemaRequest.Builder unused = new SetSchemaRequest.Builder(request)
+                .setSchemaTypeVisibilityForPackage("type1", /*visible=*/ true,
+                        otherPackageIdentifier)
+                .addRequiredPermissionsForSchemaTypeVisibility("type3", Collections.singleton(
+                        SetSchemaRequest.READ_SMS))
+                .addSchemaTypeVisibleToConfig("type1",
+                        new SchemaVisibilityConfig.Builder().addAllowedPackage(
+                                otherPackageIdentifier).build());
+
+        // Validate that changing the copy builder did not affect the original request
+        assertThat(request.getSchemasVisibleToPackages()).containsExactly("type1",
+                Collections.singleton(packageIdentifier));
+        assertThat(request.getRequiredPermissionsForSchemaTypeVisibility()).containsExactly("type3",
+                Collections.singleton(Collections.singleton(SetSchemaRequest.READ_CONTACTS)));
+        assertThat(request.getSchemasVisibleToConfigs()).containsExactly("type1",
+                Collections.singleton(new SchemaVisibilityConfig.Builder().build()));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testSetSchemaRequestBuilder_clearSchemas() {
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("type1").build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("type2").build();
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(schema1, schema2)
+                .clearSchemas()
+                .build();
+        assertThat(request.getSchemas()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+    public void testSetSchemaRequestBuilder_clearMigrators() {
+        AppSearchSchema schema1 = new AppSearchSchema.Builder("type1").build();
+        AppSearchSchema schema2 = new AppSearchSchema.Builder("type2").build();
+        Migrator migrator = new NoOpMigrator();
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(schema1, schema2)
+                .setMigrator("type1", migrator)
+                .setMigrator("type2", migrator)
+                .clearMigrators()
+                .build();
+        assertThat(request.getMigrators()).isEmpty();
+    }
+
+    /** Migrator that does nothing. */
+    private static class NoOpMigrator extends Migrator {
+        @Override
+        public boolean shouldMigrate(int currentVersion, int finalVersion) {
+            return false;
+        }
+
+        @NonNull
+        @Override
+        public GenericDocument onUpgrade(int currentVersion, int finalVersion,
+                @NonNull GenericDocument document) {
+            return document;
+        }
+
+        @NonNull
+        @Override
+        public GenericDocument onDowngrade(int currentVersion, int finalVersion,
+                @NonNull GenericDocument document) {
+            return document;
+        }
+    }
+
     // @exportToFramework:startStrip()
     @Document
     static class Outer {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/AndNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/AndNodeCtsTest.java
new file mode 100644
index 0000000..17cae7e
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/AndNodeCtsTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.cts.ast.operators;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.appsearch.ast.Node;
+import androidx.appsearch.ast.TextNode;
+import androidx.appsearch.ast.operators.AndNode;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+public class AndNodeCtsTest {
+
+    @Test
+    public void testConstructor_buildsAndNode() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+
+        List<Node> childNodes = List.of(foo, bar, baz);
+
+        AndNode andNode = new AndNode(childNodes);
+        assertThat(andNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testConstructor_buildsAndNodeVarArgs() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+
+        AndNode andNode = new AndNode(foo, bar, baz);
+        assertThat(andNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNullListPassed() {
+        assertThrows(NullPointerException.class, () -> new AndNode(null));
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNotEnoughNodes() {
+        TextNode foo = new TextNode("foo");
+        assertThrows(IllegalArgumentException.class,
+                () -> new AndNode(Collections.singletonList(foo)));
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNullArgPassed() {
+        TextNode foo = new TextNode("foo");
+        TextNode baz = new TextNode("baz");
+        assertThrows(NullPointerException.class, () -> new AndNode(foo, null, baz));
+    }
+
+    @Test
+    public void testSetChildren_throwsWhenNullListPassed() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        assertThrows(NullPointerException.class, () -> andNode.setChildren(null));
+    }
+
+    @Test
+    public void testSetChildren_throwsWhenNotEnoughNodes() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> andNode.setChildren(Collections.singletonList(foo)));
+    }
+
+    @Test
+    public void testAddChild_addsToBackOfList() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        andNode.addChild(baz);
+
+        assertThat(andNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testSetChild_throwsOnOutOfRangeIndex() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        assertThrows(IllegalArgumentException.class, () -> andNode.setChild(3, baz));
+    }
+
+    @Test
+    public void testSetChild_throwsOnNullNode() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        assertThrows(NullPointerException.class, () -> andNode.setChild(0, null));
+    }
+
+    @Test
+    public void testSetChild_setCorrectChild() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        andNode.setChild(0, baz);
+
+        assertThat(andNode.getChildren()).containsExactly(baz, bar).inOrder();
+    }
+
+    @Test
+    public void testGetChildren_returnsViewOf() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        List<Node> copyChildNodes = andNode.getChildren();
+
+        // Check that initially copy is equivalent to original list
+        assertThat(copyChildNodes).containsExactly(foo, bar).inOrder();
+        // Now make changes to the original list
+        TextNode baz = new TextNode("baz");
+        andNode.setChild(1, baz);
+
+        TextNode bat = new TextNode("bat");
+        andNode.addChild(bat);
+        // Check that the copied list is the same as the original list.
+        assertThat(copyChildNodes).containsExactly(foo, baz, bat).inOrder();
+    }
+
+    @Test
+    public void testRemoveChild_throwsIfListIsTooSmall() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        AndNode andNode = new AndNode(foo, bar);
+
+        assertThrows(IllegalStateException.class, () -> andNode.removeChild(0));
+    }
+
+    @Test
+    public void testRemoveChild_throwsIfIndexOutOfRange() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+        AndNode andNode = new AndNode(foo, bar, baz);
+
+        assertThrows(IllegalArgumentException.class, () -> andNode.removeChild(-1));
+        assertThrows(IllegalArgumentException.class, () -> andNode.removeChild(3));
+    }
+}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/OrNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/OrNodeCtsTest.java
new file mode 100644
index 0000000..b954333
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/OrNodeCtsTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.cts.ast.operators;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.appsearch.ast.Node;
+import androidx.appsearch.ast.TextNode;
+import androidx.appsearch.ast.operators.OrNode;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+public class OrNodeCtsTest {
+
+    @Test
+    public void testConstructor_buildsOrNode() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+
+        List<Node> childNodes = List.of(foo, bar , baz);
+
+        OrNode orNode = new OrNode(childNodes);
+        assertThat(orNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testConstructor_buildsOrNodeVarArgs() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+
+        OrNode orNode = new OrNode(foo, bar, baz);
+        assertThat(orNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNullListPassed() {
+        assertThrows(NullPointerException.class, () -> new OrNode(null));
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNotEnoughNodes() {
+        TextNode foo = new TextNode("foo");
+        assertThrows(IllegalArgumentException.class,
+                () -> new OrNode(Collections.singletonList(foo)));
+    }
+
+    @Test
+    public void testConstructor_throwsWhenNullArgPassed() {
+        TextNode foo = new TextNode("foo");
+        TextNode baz = new TextNode("baz");
+        assertThrows(NullPointerException.class,
+                () -> new OrNode(foo, null, baz));
+    }
+
+    @Test
+    public void testSetChildren_throwsWhenNullListPassed() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        assertThrows(NullPointerException.class, () -> orNode.setChildren(null));
+    }
+
+    @Test
+    public void testSetChildren_throwsWhenNotEnoughNodes() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> orNode.setChildren(Collections.singletonList(new TextNode("baz"))));
+    }
+
+    @Test
+    public void testAddChild_addsToBackOfList() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        orNode.addChild(baz);
+
+        assertThat(orNode.getChildren()).containsExactly(foo, bar, baz).inOrder();
+    }
+
+    @Test
+    public void testSetChild_throwsOnOutOfRangeIndex() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        assertThrows(IllegalArgumentException.class, () -> orNode.setChild(3, baz));
+    }
+
+    @Test
+    public void testSetChild_throwsOnNullNode() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        assertThrows(NullPointerException.class, () -> orNode.setChild(0, null));
+    }
+
+    @Test
+    public void testSetChild_setCorrectChild() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        TextNode baz = new TextNode("baz");
+        orNode.setChild(0, baz);
+        assertThat(orNode.getChildren()).containsExactly(baz, bar).inOrder();
+    }
+
+    @Test
+    public void testGetChildren_returnsCopy() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        List<Node> copyChildNodes = orNode.getChildren();
+
+        // Check that initially copy is equivalent to original list
+        assertThat(copyChildNodes).containsExactly(foo, bar).inOrder();
+        // Now make changes to the original list
+        TextNode baz = new TextNode("baz");
+        orNode.setChild(1, baz);
+
+        TextNode bat = new TextNode("bat");
+        orNode.addChild(bat);
+        // Check that the copied list is the same as the original list.
+        assertThat(copyChildNodes).containsExactly(foo, baz, bat).inOrder();
+    }
+
+    @Test
+    public void testRemoveChild_throwsIfListIsTooSmall() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        OrNode orNode = new OrNode(foo, bar);
+
+        assertThrows(IllegalStateException.class, () -> orNode.removeChild(0));
+    }
+
+    @Test
+    public void testRemoveChild_throwsIfIndexOutOfRange() {
+        TextNode foo = new TextNode("foo");
+        TextNode bar = new TextNode("bar");
+        TextNode baz = new TextNode("baz");
+        OrNode orNode = new OrNode(foo, bar, baz);
+
+        assertThrows(IllegalArgumentException.class, () -> orNode.removeChild(-1));
+        assertThrows(IllegalArgumentException.class, () -> orNode.removeChild(3));
+    }
+}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
index 0024d8d..e958bae 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
@@ -146,4 +146,11 @@
                 .isEqualTo("com.android.appsearch.flags"
                         + ".enable_abstract_syntax_trees");
     }
+
+    @Test
+    public void testFlagValue_enableAdditionalBuilderCopyConstructors() {
+        assertThat(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+                .isEqualTo(
+                        "com.android.appsearch.flags.enable_additional_builder_copy_constructors");
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBatchResult.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBatchResult.java
index 08a16d2..fb9a51f 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBatchResult.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBatchResult.java
@@ -19,6 +19,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.flags.FlaggedApi;
+import androidx.appsearch.flags.Flags;
 import androidx.collection.ArrayMap;
 import androidx.core.util.Preconditions;
 
@@ -131,6 +133,18 @@
         private ArrayMap<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
         private boolean mBuilt = false;
 
+        /** Creates a new {@link Builder}. */
+        public Builder() {
+        }
+
+        /** Creates a new {@link Builder} from the given {@link AppSearchBatchResult}. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        public Builder(@NonNull AppSearchBatchResult<KeyType, ValueType> appSearchBatchResult) {
+            mSuccesses.putAll(appSearchBatchResult.mSuccesses);
+            mFailures.putAll(appSearchBatchResult.mFailures);
+            mAll.putAll(appSearchBatchResult.mAll);
+        }
+
         /**
          * Associates the {@code key} with the provided successful return value.
          *
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
index b300d3e..c0104a0 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -51,7 +51,6 @@
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -89,10 +88,10 @@
             @Param(id = 2) @NonNull List<PropertyConfigParcel> propertyConfigParcels,
             @Param(id = 3) @NonNull List<String> parentTypes,
             @Param(id = 4) @NonNull String description) {
-        mSchemaType = Objects.requireNonNull(schemaType);
-        mPropertyConfigParcels = Objects.requireNonNull(propertyConfigParcels);
-        mParentTypes = Objects.requireNonNull(parentTypes);
-        mDescription = Objects.requireNonNull(description);
+        mSchemaType = Preconditions.checkNotNull(schemaType);
+        mPropertyConfigParcels = Preconditions.checkNotNull(propertyConfigParcels);
+        mParentTypes = Preconditions.checkNotNull(parentTypes);
+        mDescription = Preconditions.checkNotNull(description);
     }
 
     @Override
@@ -225,7 +224,7 @@
 
     /** Builder for {@link AppSearchSchema objects}. */
     public static final class Builder {
-        private final String mSchemaType;
+        private String mSchemaType;
         private String mDescription = "";
         private ArrayList<PropertyConfigParcel> mPropertyConfigParcels = new ArrayList<>();
         private LinkedHashSet<String> mParentTypes = new LinkedHashSet<>();
@@ -238,6 +237,31 @@
         }
 
         /**
+         * Creates a new {@link AppSearchSchema.Builder} from the given {@link AppSearchSchema}.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        public Builder(@NonNull AppSearchSchema schema) {
+            mSchemaType = schema.getSchemaType();
+            mDescription = schema.getDescription();
+            mPropertyConfigParcels.addAll(schema.mPropertyConfigParcels);
+            mParentTypes.addAll(schema.mParentTypes);
+            for (int i = 0; i < mPropertyConfigParcels.size(); i++) {
+                mPropertyNames.add(mPropertyConfigParcels.get(i).getName());
+            }
+        }
+
+        /** Sets the schema type name. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public AppSearchSchema.Builder setSchemaType(@NonNull String schemaType) {
+            Preconditions.checkNotNull(schemaType);
+            resetIfBuilt();
+            mSchemaType = schemaType;
+            return this;
+        }
+
+        /**
          * Sets a natural language description of this schema type.
          *
          * <p> For more details about the description field, see {@link
@@ -250,7 +274,7 @@
         @CanIgnoreReturnValue
         @NonNull
         public AppSearchSchema.Builder setDescription(@NonNull String description) {
-            Objects.requireNonNull(description);
+            Preconditions.checkNotNull(description);
             resetIfBuilt();
             mDescription = description;
             return this;
@@ -271,6 +295,19 @@
         }
 
         /**
+         * Clears all properties from the given type.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public AppSearchSchema.Builder clearProperties() {
+            resetIfBuilt();
+            mPropertyConfigParcels.clear();
+            mPropertyNames.clear();
+            return this;
+        }
+
+        /**
          * Adds a parent type to the given type for polymorphism, so that the given type will be
          * considered as a subtype of {@code parentSchemaType}.
          *
@@ -341,6 +378,18 @@
             return this;
         }
 
+        /**
+         * Clears all parent types from the given type.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public AppSearchSchema.Builder clearParentTypes() {
+            resetIfBuilt();
+            mParentTypes.clear();
+            return this;
+        }
+
         /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
         @NonNull
         public AppSearchSchema build() {
@@ -872,7 +921,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public StringPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1111,7 +1160,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public LongPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1211,7 +1260,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public DoublePropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1273,7 +1322,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public BooleanPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1335,7 +1384,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public BytesPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1459,7 +1508,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public DocumentPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
@@ -1721,7 +1770,7 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public EmbeddingPropertyConfig.Builder setDescription(@NonNull String description) {
-                mDescription = Objects.requireNonNull(description);
+                mDescription = Preconditions.checkNotNull(description);
                 return this;
             }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GetSchemaResponse.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GetSchemaResponse.java
index 74f370e..0b0ae88 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GetSchemaResponse.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GetSchemaResponse.java
@@ -348,11 +348,27 @@
         private Map<String, InternalVisibilityConfig.Builder> mVisibilityConfigBuilders;
         private boolean mBuilt = false;
 
-        /** Create a {@link Builder} object} */
+        /** Creates a new {@link Builder} */
         public Builder() {
             setVisibilitySettingSupported(true);
         }
 
+        /** Creates a new {@link Builder} from the given {@link GetSchemaResponse}. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        public Builder(@NonNull GetSchemaResponse getSchemaResponse) {
+            setVisibilitySettingSupported(true);
+            mVersion = getSchemaResponse.mVersion;
+            mSchemas.addAll(getSchemaResponse.mSchemas);
+            if (getSchemaResponse.mVisibilityConfigs != null) {
+                int count = getSchemaResponse.mVisibilityConfigs.size();
+                for (int i = 0; i < count; i++) {
+                    InternalVisibilityConfig config = getSchemaResponse.mVisibilityConfigs.get(i);
+                    mVisibilityConfigBuilders.put(config.getSchemaType(),
+                            new InternalVisibilityConfig.Builder(config));
+                }
+            }
+        }
+
         /**
          * Sets the database overall schema version.
          *
@@ -361,12 +377,13 @@
         @CanIgnoreReturnValue
         @NonNull
         public Builder setVersion(@IntRange(from = 0) int version) {
+            Preconditions.checkArgument(version >= 0, "Version must be a non-negative number.");
             resetIfBuilt();
             mVersion = version;
             return this;
         }
 
-        /**  Adds one {@link AppSearchSchema} to the schema list.  */
+        /** Adds one {@link AppSearchSchema} to the schema list. */
         @CanIgnoreReturnValue
         @NonNull
         public Builder addSchema(@NonNull AppSearchSchema schema) {
@@ -376,6 +393,16 @@
             return this;
         }
 
+        /** Clears all {@link AppSearchSchema}s from the list of schemas. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSchemas() {
+            resetIfBuilt();
+            mSchemas.clear();
+            return this;
+        }
+
         /**
          * Sets whether or not documents from the provided {@code schemaType} will be displayed
          * and visible on any system UI surface.
@@ -552,6 +579,45 @@
         }
 
         /**
+         * Clears the visibility settings configured through
+         * {@link Builder#addSchemaTypeNotDisplayedBySystem},
+         * {@link Builder#setSchemaTypeVisibleToPackages},
+         * {@link Builder#setRequiredPermissionsForSchemaTypeVisibility},
+         * {@link Builder#setPubliclyVisibleSchema(String, PackageIdentifier)}, and
+         * {@link Builder#setSchemaTypeVisibleToConfigs} for the given {@code schemaType}.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSchemaTypeVisibilityConfig(@NonNull String schemaType) {
+            Preconditions.checkNotNull(schemaType);
+            resetIfBuilt();
+            if (mVisibilityConfigBuilders != null) {
+                mVisibilityConfigBuilders.remove(schemaType);
+            }
+            return this;
+        }
+
+        /**
+         * Clears the visibility settings configured through
+         * {@link Builder#addSchemaTypeNotDisplayedBySystem},
+         * {@link Builder#setSchemaTypeVisibleToPackages},
+         * {@link Builder#setRequiredPermissionsForSchemaTypeVisibility},
+         * {@link Builder#setPubliclyVisibleSchema(String, PackageIdentifier)}, and
+         * {@link Builder#setSchemaTypeVisibleToConfigs} for all schema types.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSchemaTypeVisibilityConfigs() {
+            resetIfBuilt();
+            if (mVisibilityConfigBuilders != null) {
+                mVisibilityConfigBuilders.clear();
+            }
+            return this;
+        }
+
+        /**
          * Method to set visibility setting. If this is called with false,
          * {@link #getRequiredPermissionsForSchemaTypeVisibility()},
          * {@link #getSchemaTypesNotDisplayedBySystem()}}, and
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/JoinSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/JoinSpec.java
index 16aff14..bcebd55 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/JoinSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/JoinSpec.java
@@ -282,7 +282,7 @@
 
         private String mNestedQuery = "";
         private SearchSpec mNestedSearchSpec = EMPTY_SEARCH_SPEC;
-        private final String mChildPropertyExpression;
+        private String mChildPropertyExpression;
         private int mMaxJoinedResultCount = DEFAULT_MAX_JOINED_RESULT_COUNT;
         @AggregationScoringStrategy
         private int mAggregationScoringStrategy =
@@ -291,7 +291,7 @@
         /**
          * Create a specification for the joining operation in search.
          *
-         * <p> The child property expressions Specifies how to join documents. Documents with
+         * <p> The child property expression specifies how to join documents. Documents with
          * a child property expression equal to the qualified id of the parent will be retrieved.
          *
          * <p> Property expressions differ from {@link PropertyPath} as property expressions may
@@ -317,8 +317,8 @@
             mChildPropertyExpression = childPropertyExpression;
         }
 
-        /** @exportToFramework:hide */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        /** Creates a new {@link Builder} from the given {@link JoinSpec}. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
         public Builder(@NonNull JoinSpec joinSpec) {
             Preconditions.checkNotNull(joinSpec);
             mNestedQuery = joinSpec.getNestedQuery();
@@ -329,6 +329,33 @@
         }
 
         /**
+         * Sets the child property expression.
+         *
+         * <p> The child property expression specifies how to join documents. Documents with
+         * a child property expression equal to the qualified id of the parent will be retrieved.
+         *
+         * <p> Property expressions differ from {@link PropertyPath} as property expressions may
+         * refer to document properties or nested document properties such as "person.business.id"
+         * as well as a property expression. Currently the only property expression is
+         * "this.qualifiedId()". {@link PropertyPath} objects may only reference document properties
+         * and nested document properties.
+         *
+         * <p> In order to join a child document to a parent document, the child document must
+         * contain the parent's qualified id at the property expression specified by this
+         * method.
+         *
+         * @param childPropertyExpression the property to match in the child documents.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setChildPropertyExpression(@NonNull String childPropertyExpression) {
+            Preconditions.checkNotNull(childPropertyExpression);
+            mChildPropertyExpression = childPropertyExpression;
+            return this;
+        }
+
+        /**
          * Sets the query and the SearchSpec for the documents being joined. This will score and
          * rank the joined documents as well as filter the joined documents.
          *
@@ -356,7 +383,6 @@
             Preconditions.checkNotNull(nestedSearchSpec);
             mNestedQuery = nestedQuery;
             mNestedSearchSpec = nestedSearchSpec;
-
             return this;
         }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index 2e71571..ca736d9 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -636,7 +636,7 @@
     /**
      * Get the maximum number of results to return for each group.
      *
-     * @return the maximum number of results to return for each group or Integer.MAX_VALUE if
+     * @return the maximum number of results to return for each group or 0 if
      * {@link Builder#setResultGrouping(int, int)} was not called.
      */
     public int getResultGroupingLimit() {
@@ -804,12 +804,12 @@
         @Nullable private String mSearchSourceLogTag;
         private boolean mBuilt = false;
 
-        /** Constructs a new builder for {@link SearchSpec} objects. */
+        /** Constructs a new {@link Builder} for {@link SearchSpec} objects. */
         public Builder() {
         }
 
-        /** @exportToFramework:hide */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        /** Constructs a new {@link Builder} from the given {@link SearchSpec}. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
         public Builder(@NonNull SearchSpec searchSpec) {
             Objects.requireNonNull(searchSpec);
             mSchemas = new ArrayList<>(searchSpec.getFilterSchemas());
@@ -945,6 +945,16 @@
         }
 // @exportToFramework:endStrip()
 
+        /** Clears all schema type filters. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearFilterSchemas() {
+            resetIfBuilt();
+            mSchemas.clear();
+            return this;
+        }
+
         /**
          * Adds property paths for the specified type to the property filter of
          * {@link SearchSpec} Entry. Only returns documents that have matches under
@@ -1078,6 +1088,16 @@
         }
 // @exportToFramework:endStrip()
 
+        /** Clears the property filters for all schema types. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearFilterProperties() {
+            resetIfBuilt();
+            mTypePropertyFilters.clear();
+            return this;
+        }
+
         /**
          * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
          * have the specified namespaces.
@@ -1105,6 +1125,16 @@
             return this;
         }
 
+        /** Clears all namespace filters. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearFilterNamespaces() {
+            resetIfBuilt();
+            mNamespaces.clear();
+            return this;
+        }
+
         /**
          * Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
          * were indexed from the specified packages.
@@ -1138,6 +1168,16 @@
             return this;
         }
 
+        /** Clears all package name filters. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearFilterPackageNames() {
+            resetIfBuilt();
+            mPackageNames.clear();
+            return this;
+        }
+
         /**
          * Sets the number of results per page in the returned object.
          *
@@ -1359,6 +1399,16 @@
             return this;
         }
 
+        /** Clears all informational ranking expressions. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearInformationalRankingExpressions() {
+            resetIfBuilt();
+            mInformationalRankingExpressions.clear();
+            return this;
+        }
+
         /**
          * Sets an optional log tag to indicate the source of this search.
          *
@@ -1393,6 +1443,16 @@
             return this;
         }
 
+        /** Clears the log tag that indicates the source of this search. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSearchSourceLogTag() {
+            resetIfBuilt();
+            mSearchSourceLogTag = null;
+            return this;
+        }
+
         /**
          * Sets the order of returned search results, the default is
          * {@link #ORDER_DESCENDING}, meaning that results with higher scores come first.
@@ -1629,6 +1689,16 @@
         }
 // @exportToFramework:endStrip()
 
+        /** Clears the projections for all schema types. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearProjections() {
+            resetIfBuilt();
+            mProjectionTypePropertyMasks.clear();
+            return this;
+        }
+
         /**
          * Sets the maximum number of results to return for each group, where groups are defined
          * by grouping type.
@@ -1659,6 +1729,17 @@
             return this;
         }
 
+        /** Clears the result grouping and limit. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearResultGrouping() {
+            resetIfBuilt();
+            mGroupingTypeFlags = 0;
+            mGroupingLimit = 0;
+            return this;
+        }
+
         /**
          * Sets property weights by schema type and property path.
          *
@@ -1714,6 +1795,16 @@
             return this;
         }
 
+        /** Clears the property weights for all schema types. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearPropertyWeights() {
+            resetIfBuilt();
+            mTypePropertyWeights.clear();
+            return this;
+        }
+
         /**
          * Specifies which documents to join with, and how to join.
          *
@@ -1733,6 +1824,16 @@
             return this;
         }
 
+        /** Clears the {@link JoinSpec}. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearJoinSpec() {
+            resetIfBuilt();
+            mJoinSpec = null;
+            return this;
+        }
+
         /**
          * Sets property weights by schema type and property path.
          *
@@ -1920,6 +2021,16 @@
             return this;
         }
 
+        /** Clears the embedding parameters. */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearEmbeddingParameters() {
+            resetIfBuilt();
+            mEmbeddingParameters.clear();
+            return this;
+        }
+
         /**
          * Sets the default embedding metric type used for embedding search
          * (see {@link AppSearchSession#search}) and ranking
@@ -1984,6 +2095,19 @@
         }
 
         /**
+         * Clears the list of String parameters that can be referenced in the query through the
+         * "getSearchStringParameter({index})" function.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSearchStringParameters() {
+            resetIfBuilt();
+            mSearchStringParameters.clear();
+            return this;
+        }
+
+        /**
          * Sets the NUMERIC_SEARCH feature as enabled/disabled according to the enabled parameter.
          *
          * @param enabled Enables the feature if true, otherwise disables it.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
index 4cb1019..67140f2 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
@@ -407,6 +407,32 @@
         private int mVersion = DEFAULT_VERSION;
         private boolean mBuilt = false;
 
+        /** Creates a new {@link SetSchemaRequest.Builder}. */
+        public Builder() {
+        }
+
+        /**
+         * Creates a {@link SetSchemaRequest.Builder} from the given {@link SetSchemaRequest}.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        public Builder(@NonNull SetSchemaRequest request) {
+            mSchemas.addAll(request.mSchemas);
+            mSchemasNotDisplayedBySystem.addAll(request.mSchemasNotDisplayedBySystem);
+            for (Map.Entry<String, Set<PackageIdentifier>> entry
+                    : request.mSchemasVisibleToPackages.entrySet()) {
+                mSchemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
+            }
+            mSchemasVisibleToPermissions = deepCopy(request.mSchemasVisibleToPermissions);
+            mPubliclyVisibleSchemas.putAll(request.mPubliclyVisibleSchemas);
+            for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
+                    request.mSchemasVisibleToConfigs.entrySet()) {
+                mSchemaVisibleToConfigs.put(entry.getKey(), new ArraySet<>(entry.getValue()));
+            }
+            mMigrators.putAll(request.mMigrators);
+            mForceOverride = request.mForceOverride;
+            mVersion = request.mVersion;
+        }
+
         /**
          * Adds one or more {@link AppSearchSchema} types to the schema.
          *
@@ -507,6 +533,18 @@
 // @exportToFramework:endStrip()
 
         /**
+         * Clears all {@link AppSearchSchema}s from the list of schemas.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearSchemas() {
+            resetIfBuilt();
+            mSchemas.clear();
+            return this;
+        }
+
+        /**
          * Sets whether or not documents from the provided {@code schemaType} will be displayed
          * and visible on any system UI surface.
          *
@@ -714,10 +752,28 @@
 
 // @exportToFramework:startStrip()
         /**
-         * Specify that the schema should be publicly available, to packages which already have
-         * visibility to {@code packageIdentifier}.
+         * Specify that the documents from the provided
+         * {@link androidx.appsearch.annotation.Document} annotated class should be publicly
+         * available, to packages which already have visibility to {@code packageIdentifier}. This
+         * visibility is determined by the result of
+         * {@link android.content.pm.PackageManager#canPackageQuery}.
          *
-         * @param documentClass the document to make publicly accessible.
+         * <p> It is possible for the packageIdentifier parameter to be different from the
+         * package performing the indexing. This might happen in the case of an on-device indexer
+         * processing information about various packages. The visibility will be the same
+         * regardless of which package indexes the document, as the visibility is based on the
+         * packageIdentifier parameter.
+         *
+         * <p> If this is called repeatedly with the same
+         * {@link androidx.appsearch.annotation.Document} annotated class, the
+         * {@link PackageIdentifier} in the last call will be used as the "from" package for that
+         * class (or schema).
+         *
+         * <p> Calling this with packageIdentifier set to null is valid, and will remove public
+         * visibility for the class (or schema).
+         *
+         * @param documentClass the {@link androidx.appsearch.annotation.Document} annotated class
+         *                      to make publicly accessible.
          * @param packageIdentifier if an app can see this package via
          *                          PackageManager#canPackageQuery, it will be able to see the
          *                          documents of type {@code documentClass}.
@@ -861,6 +917,18 @@
             return this;
         }
 
+        /**
+         * Clears all {@link Migrator}s.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearMigrators() {
+            resetIfBuilt();
+            mMigrators.clear();
+            return this;
+        }
+
 // @exportToFramework:startStrip()
 
         /**
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java
new file mode 100644
index 0000000..4e479e0
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.ast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringDef;
+import androidx.appsearch.app.ExperimentalAppSearchApi;
+import androidx.appsearch.flags.FlaggedApi;
+import androidx.appsearch.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link Node} that represents a function.
+ *
+ * <p>Every function node will have a function name and some arguments represented as fields on
+ * the class extending {@link FunctionNode}.
+ *
+ * <p>FunctionNode should be implemented by a node that implements a specific function.
+ */
+@ExperimentalAppSearchApi
+@FlaggedApi(Flags.FLAG_ENABLE_ABSTRACT_SYNTAX_TREES)
+public interface FunctionNode extends Node {
+    /**
+     * Enums representing functions available to use in the query language.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+    })
+    @interface FunctionName {}
+
+    /**
+     * Gets the name of the node that extends the {@link FunctionNode}.
+     */
+    @NonNull
+    @FunctionName
+    String getFunctionName();
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/AndNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/AndNode.java
new file mode 100644
index 0000000..0b0e1a7
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/AndNode.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.ast.operators;
+
+import androidx.annotation.NonNull;
+import androidx.appsearch.app.ExperimentalAppSearchApi;
+import androidx.appsearch.ast.Node;
+import androidx.appsearch.flags.FlaggedApi;
+import androidx.appsearch.flags.Flags;
+import androidx.core.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link Node} that represents logical AND of nodes.
+ */
+@ExperimentalAppSearchApi
+@FlaggedApi(Flags.FLAG_ENABLE_ABSTRACT_SYNTAX_TREES)
+public final class AndNode implements Node {
+    private List<Node> mChildren;
+
+    /**
+     * Constructor for {@link AndNode} that represents logical AND over all its child nodes.
+     *
+     * @param childNodes The list of {@link Node} of at least size two representing queries to be
+     *                   logically ANDed over.
+     */
+    public AndNode(@NonNull List<Node> childNodes) {
+        Preconditions.checkNotNull(childNodes);
+        Preconditions.checkArgument(childNodes.size() >= 2,
+                /*errorMessage=*/ "Number of nodes must be at least two.");
+        mChildren = new ArrayList<>(childNodes);
+    }
+
+    /**
+     * Convenience constructor for {@link AndNode} that represents logical AND over all its
+     * child nodes and takes in a varargs of nodes.
+     *
+     * @param firstChild The first node to be ANDed over, which is required.
+     * @param secondChild The second node to be ANDed over, which is required.
+     * @param additionalChildren Additional nodes to be ANDed over, which are optional.
+     */
+    public AndNode(@NonNull Node firstChild, @NonNull Node secondChild,
+            @NonNull Node... additionalChildren) {
+        ArrayList<Node> childNodes = new ArrayList<>();
+        childNodes.add(Preconditions.checkNotNull(firstChild));
+        childNodes.add(Preconditions.checkNotNull(secondChild));
+        childNodes.addAll(List.of(Preconditions.checkNotNull(additionalChildren)));
+        mChildren = childNodes;
+    }
+
+    /**
+     * Get the list of nodes being logically ANDed over by this node.
+     */
+    @Override
+    @NonNull
+    public List<Node> getChildren() {
+        return Collections.unmodifiableList(mChildren);
+    }
+
+    /**
+     * Set the nodes being logically ANDed over by this node.
+     *
+     * @param childNodes A list of {@link Node} of at least size two representing the nodes to be
+     *                   logically ANDed over in this node.
+     */
+    public void setChildren(@NonNull List<Node> childNodes) {
+        Preconditions.checkNotNull(childNodes);
+        Preconditions.checkArgument(childNodes.size() >= 2,
+                /*errorMessage=*/ "Number of nodes must be at least two.");
+        mChildren = new ArrayList<>(childNodes);
+    }
+
+    /**
+     * Add a child node to the end of the current list of child nodes {@link #mChildren}.
+     *
+     * @param childNode A {@link Node} to add to the end of the list of child nodes.
+     */
+    public void addChild(@NonNull Node childNode) {
+        mChildren.add(Preconditions.checkNotNull(childNode));
+    }
+
+    /**
+     * Replace the child node at the provided index with the provided {@link Node}.
+     *
+     * @param index The index at which to replace the child node in the list of child nodes. Must be
+     *              in range of the size of {@link #mChildren}.
+     * @param childNode The {@link Node} that is replacing the childNode at the provided index.
+     */
+    public void setChild(int index, @NonNull Node childNode) {
+        Preconditions.checkArgumentInRange(index, /*lower=*/ 0, /*upper=*/ mChildren.size() - 1,
+                /*valueName=*/ "Index");
+        mChildren.set(index, Preconditions.checkNotNull(childNode));
+    }
+
+    /**
+     * Remove tbe child {@link Node} at the given index from the list of child nodes.
+     *
+     * <p>The list of child nodes must contain at least 3 nodes to perform this operation.
+     */
+    public void removeChild(int index) {
+        Preconditions.checkState(mChildren.size() > 2, "List of child nodes must"
+                + "contain at least 3 nodes in order to remove.");
+        Preconditions.checkArgumentInRange(index, /*lower=*/ 0, /*upper=*/ mChildren.size() - 1,
+                /*valueName=*/ "Index");
+        mChildren.remove(index);
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/OrNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/OrNode.java
new file mode 100644
index 0000000..f8bbd13
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/OrNode.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.ast.operators;
+
+import androidx.annotation.NonNull;
+import androidx.appsearch.app.ExperimentalAppSearchApi;
+import androidx.appsearch.ast.Node;
+import androidx.appsearch.flags.FlaggedApi;
+import androidx.appsearch.flags.Flags;
+import androidx.core.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link Node} that represents logical OR of nodes.
+ */
+@ExperimentalAppSearchApi
+@FlaggedApi(Flags.FLAG_ENABLE_ABSTRACT_SYNTAX_TREES)
+public final class OrNode implements Node{
+    private List<Node> mChildren;
+
+    /**
+     * Constructor for {@link OrNode} that represents logical OR over all its child nodes.
+     *
+     * @param childNodes The nodes representing queries to be logically ORed over.
+     */
+    public OrNode(@NonNull List<Node> childNodes) {
+        Preconditions.checkNotNull(childNodes);
+        Preconditions.checkArgument(childNodes.size() >= 2,
+                /*errorMessage=*/ "Number of nodes must be at least two.");
+        mChildren = new ArrayList<>(childNodes);
+    }
+
+    /**
+     * Convenience constructor for {@link OrNode} that represents logical OR over all its
+     * child nodes and takes in a varargs of nodes.
+     *
+     * @param firstChild The first node to be ORed over, which is required.
+     * @param secondChild The second node to be ORed over, which is required.
+     * @param additionalChildren Additional nodes to be ORed over, which are optional.
+     */
+    public OrNode(@NonNull Node firstChild, @NonNull Node secondChild,
+            @NonNull Node... additionalChildren) {
+        ArrayList<Node> childNodes = new ArrayList<Node>();
+        childNodes.add(Preconditions.checkNotNull(firstChild));
+        childNodes.add(Preconditions.checkNotNull(secondChild));
+        childNodes.addAll(List.of(Preconditions.checkNotNull(additionalChildren)));
+        mChildren = childNodes;
+    }
+
+    /**
+     * Get the list of nodes being logically ORed over by this node.
+     */
+    @Override
+    @NonNull
+    public List<Node> getChildren() {
+        return Collections.unmodifiableList(mChildren);
+    }
+
+    /**
+     * Set the nodes being logically ORed over by this node.
+     *
+     * @param childNodes A list of {@link Node} representing the nodes to be logically ORed over
+     *                   in this node.
+     */
+    public void setChildren(@NonNull List<Node> childNodes) {
+        Preconditions.checkNotNull(childNodes);
+        Preconditions.checkArgument(childNodes.size() >= 2,
+                /*errorMessage=*/ "Number of nodes must be at least two.");
+        mChildren =  new ArrayList<>(childNodes);
+    }
+
+    /**
+     * Add a child node to the end of the current list of child nodes {@link #mChildren}.
+     *
+     * @param childNode A {@link Node} to add to the end of the list of child nodes.
+     */
+    public void addChild(@NonNull Node childNode) {
+        mChildren.add(Preconditions.checkNotNull(childNode));
+    }
+
+    /**
+     * Replace the child node at the provided index with the provided {@link Node}.
+     *
+     * @param index The index at which to replace the child node in the list of child nodes. Must be
+     *              in range of the size of {@link #mChildren}.
+     * @param childNode The {@link Node} that is replacing the childNode at the provided index.
+     */
+    public void setChild(int index, @NonNull Node childNode) {
+        Preconditions.checkArgumentInRange(index, /*lower=*/ 0, /*upper=*/ mChildren.size() - 1,
+                /*valueName=*/ "Index");
+        mChildren.set(index, Preconditions.checkNotNull(childNode));
+    }
+
+    /**
+     * Remove tbe child {@link Node} at the given index from the list of child nodes.
+     *
+     * <p>The list of child nodes must contain at least 3 nodes to perform this operation.
+     */
+    public void removeChild(int index) {
+        Preconditions.checkState(mChildren.size() > 2, "List of child nodes must"
+                + "contain at least 3 nodes in order to remove.");
+        Preconditions.checkArgumentInRange(index, /*lower=*/ 0, /*upper=*/ mChildren.size() - 1,
+                /*valueName=*/ "Index");
+        mChildren.remove(index);
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
index 0816e9e..ddca2c1 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
@@ -154,6 +154,16 @@
     public static final String FLAG_ENABLE_ABSTRACT_SYNTAX_TREES =
             FLAG_PREFIX + "enable_abstract_syntax_trees";
 
+    /**
+     * Enables additional builder copy constructors for
+     * {@link androidx.appsearch.app.AppSearchSchema},
+     * {@link androidx.appsearch.app.SetSchemaRequest}, {@link androidx.appsearch.app.SearchSpec},
+     * {@link androidx.appsearch.app.JoinSpec}, {@link androidx.appsearch.app.AppSearchBatchResult},
+     * and {@link androidx.appsearch.app.GetSchemaResponse}.
+     */
+    public static final String FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS =
+            FLAG_PREFIX + "enable_additional_builder_copy_constructors";
+
     // Whether the features should be enabled.
     //
     // In Jetpack, those should always return true.
@@ -285,4 +295,15 @@
     public static boolean enableAbstractSyntaxTrees() {
         return true;
     }
+
+    /**
+     * Whether additional builder copy constructors for
+     * {@link androidx.appsearch.app.AppSearchSchema},
+     * {@link androidx.appsearch.app.SetSchemaRequest}, {@link androidx.appsearch.app.SearchSpec},
+     * {@link androidx.appsearch.app.JoinSpec}, {@link androidx.appsearch.app.AppSearchBatchResult},
+     * and {@link androidx.appsearch.app.GetSchemaResponse} should be enabled.
+     */
+    public static boolean enableAdditionalBuilderCopyConstructors() {
+        return true;
+    }
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index b7994e4..a849371 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -41,13 +41,24 @@
      * Note that when StartupMode.COLD is used, additional work must be performed during target app
      * startup to initialize tracing.
      */
-    private val _perfettoSdkTracingEnable: Boolean
     val perfettoSdkTracingEnable: Boolean
         get() = perfettoSdkTracingEnableOverride ?: _perfettoSdkTracingEnable
 
-    /** Allows tests to override whether full tracing is enabled */
+    private val _perfettoSdkTracingEnable: Boolean
     @VisibleForTesting var perfettoSdkTracingEnableOverride: Boolean? = null
 
+    /**
+     * Base URL for help articles for Startup Insights.
+     *
+     * This property should only be used while the Startup Insights feature is under development. It
+     * can be overridden for testing purposes using [startupInsightsHelpUrlBaseOverride].
+     */
+    val startupInsightsHelpUrlBase: String?
+        get() = startupInsightsHelpUrlBaseOverride ?: _startupInsightsHelpUrlBase
+
+    private val _startupInsightsHelpUrlBase: String?
+    @VisibleForTesting var startupInsightsHelpUrlBaseOverride: String? = null
+
     val enabledRules: Set<RuleType>
 
     enum class RuleType {
@@ -175,6 +186,9 @@
                 ?: arguments.getBenchmarkArgument("fullTracing.enable")?.toBoolean()
                 ?: false
 
+        _startupInsightsHelpUrlBase =
+            arguments.getBenchmarkArgument("startupInsights.helpUrlBase", defaultValue = null)
+
         // Transform comma-delimited list into set of suppressed errors
         // E.g. "DEBUGGABLE, UNLOCKED" -> setOf("DEBUGGABLE", "UNLOCKED")
         suppressedErrors =
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt
index f7a26ad..f34eb3e 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt
@@ -16,6 +16,7 @@
 
 package androidx.benchmark
 
+import androidx.annotation.RestrictTo
 import androidx.benchmark.perfetto.PerfettoConfig
 
 /**
@@ -33,4 +34,16 @@
     val startupInsightsConfig: StartupInsightsConfig? = null
 )
 
-@ExperimentalBenchmarkConfigApi public class StartupInsightsConfig(val isEnabled: Boolean)
+/** Configuration for startup insights. */
+@ExperimentalBenchmarkConfigApi
+public class StartupInsightsConfig(val isEnabled: Boolean) {
+    /**
+     * Base URL for linking to more information about specific startup reasons. This URL should
+     * accept a reason ID as a direct suffix. For example, a base URL of
+     * `https://developer.android.com/[...]/slow-start-reason#` could be combined with a reason ID
+     * of `MAIN_THREAD_MONITOR_CONTENTION` to create a complete URL like:
+     * `https://developer.android.com/[...]/slow-start-reason#MAIN_THREAD_MONITOR_CONTENTION`
+     */
+    val reasonHelpUrlBase: String? = Arguments.startupInsightsHelpUrlBase
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) get
+}
diff --git a/benchmark/benchmark-macro/build.gradle b/benchmark/benchmark-macro/build.gradle
index 347d615..8c0b606 100644
--- a/benchmark/benchmark-macro/build.gradle
+++ b/benchmark/benchmark-macro/build.gradle
@@ -69,7 +69,7 @@
     api("androidx.annotation:annotation:1.8.1")
 
     implementation("androidx.core:core:1.9.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.4.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.4.1")
     implementation("androidx.tracing:tracing-ktx:1.1.0")
     implementation("androidx.tracing:tracing-perfetto:1.0.0")
     implementation("androidx.tracing:tracing-perfetto-binary:1.0.0")
@@ -105,9 +105,10 @@
     kotlinOptions {
         // Enable using experimental APIs from within same version group
         freeCompilerArgs += [
+                "-opt-in=androidx.benchmark.ExperimentalBenchmarkConfigApi",
                 "-opt-in=androidx.benchmark.macro.ExperimentalMetricApi",
                 "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi",
-                "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi"
+                "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi",
         ]
     }
 }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/InsightExtensions.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/InsightExtensions.kt
index 7bca88d..d593ea6 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/InsightExtensions.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/InsightExtensions.kt
@@ -17,6 +17,8 @@
 package androidx.benchmark.macro
 
 import androidx.benchmark.Insight
+import androidx.benchmark.StartupInsightsConfig
+import java.net.URLEncoder
 import perfetto.protos.AndroidStartupMetric.SlowStartReason
 import perfetto.protos.AndroidStartupMetric.ThresholdValue.ThresholdUnit
 
@@ -26,7 +28,10 @@
  *
  * TODO(353692849): add unit tests
  */
-internal fun createInsightsIdeSummary(rawInsights: List<List<SlowStartReason>>): List<Insight> {
+internal fun createInsightsIdeSummary(
+    rawInsights: List<List<SlowStartReason>>,
+    startupInsightsConfig: StartupInsightsConfig?
+): List<Insight> {
     fun createInsightString(
         criterion: SlowStartReason,
         observed: List<IndexedValue<SlowStartReason>>
@@ -49,7 +54,20 @@
             }
 
         val criterionString = buildString {
-            append(requireNotNull(criterion.reason))
+            val reasonHelpUrlBase = startupInsightsConfig?.reasonHelpUrlBase
+            if (reasonHelpUrlBase != null) {
+                append("[")
+                append(requireNotNull(criterion.reason).replace("]", "\\]"))
+                append("]")
+                append("(")
+                append(reasonHelpUrlBase.replace(")", "\\)")) // base url
+                val reasonId = requireNotNull(criterion.reason_id).name
+                append(URLEncoder.encode(reasonId, Charsets.UTF_8.name())) // reason id as a suffix
+                append(")")
+            } else {
+                append(requireNotNull(criterion.reason))
+            }
+
             val thresholdValue = requireNotNull(expectedValue.value_)
             append(" (expected: ")
             if (thresholdUnit == ThresholdUnit.TRUE_OR_FALSE) {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 05b4710..823a9b3 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -330,7 +330,8 @@
             warningMessage = warningMessage,
             testName = uniqueName,
             measurements = measurements,
-            insights = createInsightsIdeSummary(insightsList),
+            insights =
+                createInsightsIdeSummary(insightsList, experimentalConfig?.startupInsightsConfig),
             iterationTracePaths = tracePaths,
             profilerResults = profilerResults,
             useTreeDisplayFormat = experimentalConfig?.startupInsightsConfig?.isEnabled == true
diff --git a/biometric/biometric/src/main/res/values-kk/strings.xml b/biometric/biometric/src/main/res/values-kk/strings.xml
index 24ae7d4..42b808c2 100644
--- a/biometric/biometric/src/main/res/values-kk/strings.xml
+++ b/biometric/biometric/src/main/res/values-kk/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Саусақ ізін оқу сканерін түртіңіз"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Саусақ ізін оқу сканерін түртіңіз."</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Танылмады"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"Саусақ ізі жабдығы қолжетімді емес."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"Саусақ іздері тіркелмеген."</string>
diff --git a/biometric/biometric/src/main/res/values-lt/strings.xml b/biometric/biometric/src/main/res/values-lt/strings.xml
index 180b3fe..8dc2c23 100644
--- a/biometric/biometric/src/main/res/values-lt/strings.xml
+++ b/biometric/biometric/src/main/res/values-lt/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Palieskite piršto antspaudo jutiklį"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Palieskite piršto atspaudo jutiklį"</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Neatpažinta"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"Piršto antspaudo aparatinė įranga nepasiekiama."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"Neužregistruota jokių kontrolinių kodų."</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 90a6d2b..9c101e5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -173,6 +173,7 @@
         project.configureLint()
         project.configureKtfmt()
         project.configureKotlinVersion()
+        project.configureJavaFormat()
 
         // Avoid conflicts between full Guava and LF-only Guava.
         project.configureGuavaUpgradeHandler()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/JavaFormat.kt b/buildSrc/private/src/main/kotlin/androidx/build/JavaFormat.kt
new file mode 100644
index 0000000..5d41cfc
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/JavaFormat.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.IgnoreEmptyDirectories
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFiles
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.SkipWhenEmpty
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.options.Option
+import org.gradle.process.ExecOperations
+
+fun Project.configureJavaFormat() {
+    val javaFormatClasspath = getJavaFormatConfiguration()
+    tasks.register("javaFormat", JavaFormatTask::class.java) { task ->
+        task.javaFormatClasspath.from(javaFormatClasspath)
+    }
+}
+
+private fun Project.getJavaFormatConfiguration(): FileCollection {
+    val config =
+        configurations.detachedConfiguration(
+            dependencies.create(getLibraryByName("googlejavaformat"))
+        )
+    return files(config)
+}
+
+@CacheableTask
+abstract class JavaFormatTask : DefaultTask() {
+    init {
+        description = "Fix Java code style deviations."
+        group = "formatting"
+    }
+
+    @get:Input
+    @set:Option(option = "fix-imports-only", description = "Only correct imports")
+    var importsOnly: Boolean = false
+
+    @get:Inject abstract val execOperations: ExecOperations
+
+    @get:Classpath abstract val javaFormatClasspath: ConfigurableFileCollection
+
+    @get:Inject abstract val objects: ObjectFactory
+
+    @[InputFiles PathSensitive(PathSensitivity.RELATIVE) SkipWhenEmpty IgnoreEmptyDirectories]
+    open fun getInputFiles(): FileTree {
+        return objects.fileTree().setDir(INPUT_DIR).apply {
+            include(INCLUDED_FILES)
+            exclude(excludedDirectoryGlobs)
+        }
+    }
+
+    // Format task rewrites inputs, so the outputs are the same as inputs.
+    @OutputFiles fun getRewrittenFiles(): FileTree = getInputFiles()
+
+    private fun getArgsList(): List<String> {
+        val arguments = mutableListOf("--aosp", "--replace")
+        if (importsOnly) arguments.add("--fix-imports-only")
+        arguments.addAll(getInputFiles().files.map { it.absolutePath })
+        return arguments
+    }
+
+    @TaskAction
+    fun runFormat() {
+        execOperations.javaexec { javaExecSpec ->
+            javaExecSpec.mainClass.set(MAIN_CLASS)
+            javaExecSpec.classpath = javaFormatClasspath
+            javaExecSpec.args = getArgsList()
+            javaExecSpec.jvmArgs(
+                "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+            )
+        }
+    }
+
+    companion object {
+        private val excludedDirectories =
+            listOf(
+                "test-data",
+                "external",
+            )
+
+        private val excludedDirectoryGlobs = excludedDirectories.map { "**/$it/**/*.java" }
+        private const val MAIN_CLASS = "com.google.googlejavaformat.java.Main"
+        private const val INPUT_DIR = "src"
+        private const val INCLUDED_FILES = "**/*.java"
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index d586bd4..bff4d0d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -211,7 +211,6 @@
 
 private fun Project.configureLint(lint: Lint, isLibrary: Boolean) {
     val extension = project.androidXExtension
-    val isMultiplatform = project.multiplatformExtension != null
     val lintChecksProject = findLintProject(":lint-checks") ?: return
     project.dependencies.add("lintChecks", lintChecksProject)
 
@@ -263,13 +262,6 @@
             fatal.add("VisibleForTests")
         }
 
-        if (isMultiplatform) {
-            // Disable classfile-based checks because lint cannot find the class files for
-            // multiplatform projects and `SourceSet.java.classesDirectory` is not configurable.
-            // This is not ideal, but it's better than having no lint checks at all.
-            disable.add("LintError")
-        }
-
         // Disable a check that's only relevant for apps that ship to Play Store. (b/299278101)
         disable.add("ExpiredTargetSdkVersion")
 
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
index ba370be..ca891ea 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
@@ -115,18 +115,18 @@
         testImplementation.extendsFrom(bundle)
 
         // Relocation needed to avoid classpath conflicts with Android Studio (b/337980250)
-        // Can be removed if we migrate from using kotlinx-metadata-jvm inside of lint checks
-        val relocations = listOf(Relocation("kotlinx.metadata", "androidx.lint.kotlinx.metadata"))
+        // Can be removed if we migrate from using kotlin-metadata-jvm inside of lint checks
+        val relocations = listOf(Relocation("kotlin.metadata", "androidx.lint.kotlin.metadata"))
         val repackage = configureRepackageTaskForType(relocations, bundle, null)
         val sourceSets = extensions.getByType(SourceSetContainer::class.java)
         repackage.configure { task ->
             task.from(sourceSets.findByName("main")?.output)
-            // kotlinx-metadata-jvm has a service descriptor that needs transformation
+            // kotlin-metadata-jvm has a service descriptor that needs transformation
             task.mergeServiceFiles()
-            // Exclude Kotlin metadata files from kotlinx-metadata-jvm
+            // Exclude Kotlin metadata files from kotlin-metadata-jvm
             task.exclude(
-                "META-INF/kotlinx-metadata-jvm.kotlin_module",
-                "META-INF/kotlinx-metadata.kotlin_module",
+                "META-INF/kotlin-metadata-jvm.kotlin_module",
+                "META-INF/kotlin-metadata.kotlin_module",
                 "META-INF/metadata.jvm.kotlin_module",
                 "META-INF/metadata.kotlin_module"
             )
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index af8fa23..a9bb97d 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -64,17 +64,6 @@
   fi
 }
 
-# export some variables depending on the platform
-if [[ "$(uname)" == Darwin* ]]; then
-  ANDROID_HOME=../../prebuilts/fullsdk-darwin
-else
-  ANDROID_HOME=../../prebuilts/fullsdk-linux
-  # Remove when b/365535238 is fixed as the Linux image will contain Python3
-  export PATH="$ANDROID_HOME/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/python3/bin:$PATH"
-  # Remove when b/366010045 is resolved: android platform build requires either en_US.UTF-8 or C.UTF-8 to exist
-  export LC_ALL=C.UTF-8
-fi
-
 BUILD_STATUS=0
 # enable remote build cache unless explicitly disabled
 if [ "$USE_ANDROIDX_REMOTE_BUILD_CACHE" == "" ]; then
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
index 02b76ee..96895c8 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CapturePipelineTest.kt
@@ -60,7 +60,7 @@
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraCaptureResultImageInfo
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import androidx.camera.testing.impl.mocks.MockScreenFlash
 import androidx.concurrent.futures.await
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index 263b7a5..e2356fb 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -44,9 +44,10 @@
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.core.internal.compat.workaround.CaptureFailedRetryEnabler;
 import androidx.camera.testing.fakes.FakeCamera;
+import androidx.camera.testing.fakes.FakeCameraCaptureResult;
 import androidx.camera.testing.fakes.FakeCameraControl;
+import androidx.camera.testing.imagecapture.CaptureResult;
 import androidx.camera.testing.impl.CoreAppTestUtil;
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
 import androidx.camera.testing.impl.fakes.FakeCameraCoordinator;
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager;
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory;
@@ -126,7 +127,7 @@
 
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             // Notify the cancel after the capture request has been successfully submitted
-            fakeCameraControl.notifyAllRequestsOnCaptureCancelled();
+            fakeCameraControl.completeAllCaptureRequests(CaptureResult.cancelledResult());
         });
 
         mInstrumentation.runOnMainSync(
@@ -156,7 +157,7 @@
                 getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             // Notify the failure after the capture request has been successfully submitted
-            fakeCameraControl.notifyAllRequestsOnCaptureFailed();
+            fakeCameraControl.completeAllCaptureRequests(CaptureResult.failedResult());
         });
 
         mInstrumentation.runOnMainSync(
@@ -304,7 +305,7 @@
         // Simulates the case that the capture request failed after running in 300 ms.
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             CameraXExecutors.mainThreadExecutor().schedule(() -> {
-                fakeCameraControl.notifyAllRequestsOnCaptureFailed();
+                fakeCameraControl.completeAllCaptureRequests(CaptureResult.failedResult());
             }, 300, TimeUnit.MILLISECONDS);
         });
 
@@ -482,7 +483,7 @@
         CaptureFailedRetryEnabler retryEnabler = new CaptureFailedRetryEnabler();
         // Because of retry in some devices, we may need to notify capture failures multiple times.
         addExtraFailureNotificationsForRetry(fakeCameraControl, retryEnabler.getRetryCount());
-        fakeCameraControl.notifyAllRequestsOnCaptureFailed();
+        fakeCameraControl.completeAllCaptureRequests(CaptureResult.failedResult());
 
         // Assert.
         verify(callback, timeout(1000).times(1)).onError(any());
@@ -494,7 +495,7 @@
         if (retryCount > 0) {
             cameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
                 addExtraFailureNotificationsForRetry(cameraControl, retryCount - 1);
-                cameraControl.notifyAllRequestsOnCaptureFailed();
+                cameraControl.completeAllCaptureRequests(CaptureResult.failedResult());
             });
         }
     }
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
index 966b690..c8653a1 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
@@ -27,8 +27,8 @@
 import androidx.camera.core.impl.ImageReaderProxy;
 import androidx.camera.core.impl.TagBundle;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.testing.fakes.FakeCameraCaptureResult;
 import androidx.camera.testing.impl.HandlerUtil;
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
 import androidx.camera.testing.impl.fakes.FakeImageReaderProxy;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
index 260965f..065ac04 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
@@ -34,6 +34,7 @@
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.TIMESTAMP
 import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.Quirks
 import androidx.camera.core.impl.utils.Exif
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -144,7 +145,7 @@
     private suspend fun processYuvAndVerifyOutputSize(outputFileOptions: OutputFileOptions?) {
         // Arrange: create node with JPEG input and grayscale effect.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.YUV_420_888, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.YUV_420_888, listOf(ImageFormat.JPEG))
         val imageIn =
             createYuvFakeImageProxy(
                 CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
@@ -167,7 +168,7 @@
                 null,
                 InternalImageProcessor(GrayscaleImageEffect())
             )
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         val imageIn =
             createJpegFakeImageProxy(
                 CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
@@ -194,11 +195,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -220,18 +223,20 @@
     ) {
         // Arrange: create a request with no cropping
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -260,18 +265,20 @@
         // Arrange: create a request with no cropping
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -306,18 +313,20 @@
     private suspend fun inMemoryInputPacket_callbackInvoked(outputFileOptions: OutputFileOptions?) {
         // Arrange.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -346,18 +355,20 @@
         // Arrange.
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -385,7 +396,7 @@
     private suspend fun saveJpegOnDisk_verifyOutput(outputFileOptions: OutputFileOptions?) {
         // Arrange: create a on-disk processing request.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
         val jpegBytes =
@@ -395,11 +406,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -425,7 +438,7 @@
         // Arrange: create a on-disk processing request.
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
         val jpegBytes =
@@ -438,11 +451,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -483,18 +498,20 @@
         // Force inject the quirk for the A24 incorrect JPEG metadata problem
         val node =
             ProcessingNode(mainThreadExecutor(), Quirks(listOf(IncorrectJpegMetadataQuirk())), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                null,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    /*outputFileOptions=*/ null,
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
index a3672fd..f59512c 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
@@ -20,9 +20,11 @@
 import android.graphics.Rect
 import android.util.Size
 import androidx.camera.core.ImageCapture
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import java.io.File
 import java.util.UUID
+import org.mockito.Mockito.mock
 
 object Utils {
     const val WIDTH = 640
@@ -46,4 +48,35 @@
             "hdrgm:Version=",
             "Item:Semantic=\"GainMap\"",
         )
+
+    fun createTakePictureRequest(
+        outputFileOptions: List<ImageCapture.OutputFileOptions>?,
+        cropRect: Rect,
+        sensorToBufferTransform: Matrix,
+        rotationDegrees: Int,
+        jpegQuality: Int,
+        isSimultaneousCapture: Boolean = false
+    ): TakePictureRequest {
+        var onDiskCallback: ImageCapture.OnImageSavedCallback? = null
+        var onMemoryCallback: ImageCapture.OnImageCapturedCallback? = null
+        if (outputFileOptions == null) {
+            onMemoryCallback = mock(ImageCapture.OnImageCapturedCallback::class.java)
+        } else {
+            onDiskCallback = mock(ImageCapture.OnImageSavedCallback::class.java)
+        }
+
+        return TakePictureRequest.of(
+            CameraXExecutors.mainThreadExecutor(),
+            onMemoryCallback,
+            onDiskCallback,
+            outputFileOptions,
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality,
+            ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+            isSimultaneousCapture,
+            listOf()
+        )
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index bd994cb..72c23be 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -47,6 +47,7 @@
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
+import static androidx.camera.core.impl.ImageInputConfig.OPTION_SECONDARY_INPUT_FORMAT;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_CUSTOM_ORDERED_RESOLUTIONS;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_TYPE;
@@ -319,6 +320,13 @@
     public static final int OUTPUT_FORMAT_RAW = 2;
 
     /**
+     * Captures raw images in the {@link ImageFormat#RAW_SENSOR} and {@link ImageFormat#JPEG}
+     * image formats.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static final int OUTPUT_FORMAT_RAW_JPEG = 3;
+
+    /**
      * Provides a static configuration with implementation-agnostic options.
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -479,6 +487,9 @@
         } else {
             if (isOutputFormatRaw(builder.getMutableConfig())) {
                 builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+            } else if (isOutputFormatRawJpeg(builder.getMutableConfig())) {
+                builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                builder.getMutableConfig().insertOption(OPTION_SECONDARY_INPUT_FORMAT, JPEG);
             } else if (isOutputFormatUltraHdr(builder.getMutableConfig())) {
                 builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
                 builder.getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
@@ -531,6 +542,11 @@
                 OUTPUT_FORMAT_RAW);
     }
 
+    private static boolean isOutputFormatRawJpeg(@NonNull MutableConfig config) {
+        return Objects.equals(config.retrieveOption(OPTION_OUTPUT_FORMAT, null),
+                OUTPUT_FORMAT_RAW_JPEG);
+    }
+
     /**
      * Configures flash mode to CameraControlInternal once it is ready.
      */
@@ -887,6 +903,11 @@
      *
      * <p>The listener is responsible for calling {@link Image#close()} on the returned image.
      *
+     * <p>For simultaneous image capture with {@link #OUTPUT_FORMAT_RAW_JPEG}, the
+     * {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)} will be triggered twice, one for
+     * {@link ImageFormat#RAW_SENSOR} and the other for {@link ImageFormat#JPEG}. The order in which
+     * image format is triggered first is not guaranteed.
+     *
      * @param executor The executor in which the callback methods will be run.
      * @param callback Callback to be invoked for the newly captured image.
      *
@@ -923,9 +944,34 @@
             final @NonNull OutputFileOptions outputFileOptions,
             final @NonNull Executor executor,
             final @NonNull OnImageSavedCallback imageSavedCallback) {
+        takePicture(List.of(outputFileOptions), executor, imageSavedCallback);
+    }
+
+    /**
+     * Captures two still images simultaneously and saves to a file along with application
+     * specified metadata.
+     *
+     * <p>Currently only {@link #OUTPUT_FORMAT_RAW_JPEG} is supporting simultaneous image capture.
+     * It needs two {@link OutputFileOptions}, the first one is used for
+     * {@link ImageFormat#RAW_SENSOR} image and the second one is for {@link ImageFormat#JPEG}. The
+     * order in which image format is triggered first is not guaranteed.
+     *
+     * @param outputFileOptions  List of options to store the newly captured images.
+     * @param executor           The executor in which the callback methods will be run.
+     * @param imageSavedCallback Callback to be called for the newly captured image.
+     *
+     * @throws IllegalArgumentException If {@link ImageCapture#FLASH_MODE_SCREEN} is used without a
+     *                                  a non-null {@code ScreenFlash} instance set.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public void takePicture(
+            final @NonNull List<OutputFileOptions> outputFileOptions,
+            final @NonNull Executor executor,
+            final @NonNull OnImageSavedCallback imageSavedCallback) {
         if (Looper.getMainLooper() != Looper.myLooper()) {
             CameraXExecutors.mainThreadExecutor().execute(
-                    () -> takePicture(outputFileOptions, executor, imageSavedCallback));
+                    () -> takePicture(outputFileOptions,
+                            executor, imageSavedCallback));
             return;
         }
         takePictureInternal(executor, /*inMemoryCallback=*/null, imageSavedCallback,
@@ -1000,6 +1046,7 @@
 
             if (isRawSupported()) {
                 formats.add(OUTPUT_FORMAT_RAW);
+                formats.add(OUTPUT_FORMAT_RAW_JPEG);
             }
 
             return formats;
@@ -1407,7 +1454,7 @@
     private void takePictureInternal(@NonNull Executor executor,
             @Nullable OnImageCapturedCallback inMemoryCallback,
             @Nullable ImageCapture.OnImageSavedCallback onDiskCallback,
-            @Nullable OutputFileOptions outputFileOptions) {
+            @Nullable List<OutputFileOptions> outputFileOptions) {
         checkMainThread();
         if (getFlashMode() == ImageCapture.FLASH_MODE_SCREEN
                 && mScreenFlashWrapper.getBaseScreenFlash() == null) {
@@ -1429,6 +1476,7 @@
                 getRelativeRotation(camera),
                 getJpegQualityInternal(),
                 getCaptureMode(),
+                getCurrentConfig().getSecondaryInputFormat() != ImageFormat.UNKNOWN,
                 mSessionConfigBuilder.getSingleCameraCaptureCallbacks()));
     }
 
@@ -1644,7 +1692,8 @@
      */
     @OptIn(markerClass = androidx.camera.core.ExperimentalImageCaptureOutputFormat.class)
     @Target({ElementType.TYPE_USE})
-    @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_JPEG_ULTRA_HDR, OUTPUT_FORMAT_RAW})
+    @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_JPEG_ULTRA_HDR,
+            OUTPUT_FORMAT_RAW, OUTPUT_FORMAT_RAW_JPEG})
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public @interface OutputFormat {
@@ -2368,6 +2417,9 @@
             } else {
                 if (isOutputFormatRaw(getMutableConfig())) {
                     getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                } else if (isOutputFormatRawJpeg(getMutableConfig())) {
+                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                    getMutableConfig().insertOption(OPTION_SECONDARY_INPUT_FORMAT, JPEG);
                 } else if (isOutputFormatUltraHdr(getMutableConfig())) {
                     getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
                     getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
index b81133b..62245fd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
@@ -49,10 +49,11 @@
 public final class ImageProcessingUtil {
 
     private static final String TAG = "ImageProcessingUtil";
+    public static final String JNI_LIB_NAME = "image_processing_util_jni";
     private static int sImageCount = 0;
 
     static {
-        System.loadLibrary("image_processing_util_jni");
+        System.loadLibrary(JNI_LIB_NAME);
     }
 
     enum Result {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
index d4ab73e..ceec2f8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.processing.Operation;
 import androidx.camera.core.processing.Packet;
@@ -37,7 +38,8 @@
  *
  * <p>The {@link Bitmap} will be recycled and should not be used after the processing.
  */
-class Bitmap2JpegBytes implements Operation<Bitmap2JpegBytes.In, Packet<byte[]>> {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class Bitmap2JpegBytes implements Operation<Bitmap2JpegBytes.In, Packet<byte[]>> {
 
     @NonNull
     @Override
@@ -79,16 +81,16 @@
      * Input of {@link Bitmap2JpegBytes} processor.
      */
     @AutoValue
-    abstract static class In {
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public abstract static class In {
 
         abstract Packet<Bitmap> getPacket();
 
         abstract int getJpegQuality();
 
         @NonNull
-        static In of(@NonNull Packet<Bitmap> imagePacket, int jpegQuality) {
+        public static In of(@NonNull Packet<Bitmap> imagePacket, int jpegQuality) {
             return new AutoValue_Bitmap2JpegBytes_In(imagePacket, jpegQuality);
         }
     }
 }
-
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
index c66c031..595ab76 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.core.imagecapture;
 
+import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.RAW_SENSOR;
+
 import static androidx.camera.core.ImageCapture.ERROR_CAPTURE_FAILED;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
@@ -54,6 +57,8 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.List;
+
 /**
  * A {@link Node} that calls back when all the images for one capture are received.
  *
@@ -79,6 +84,10 @@
     @Nullable
     SafeCloseImageReaderProxy mSafeCloseImageReaderProxy;
 
+    /* Additional image reader for simultaneous RAW + JPEG capture */
+    @Nullable
+    SafeCloseImageReaderProxy mSecondarySafeCloseImageReaderProxy;
+
     @Nullable
     SafeCloseImageReaderProxy mSafeCloseImageReaderForPostview;
 
@@ -88,6 +97,7 @@
     private In mInputEdge;
     @Nullable
     private NoMetadataImageReader mNoMetadataImageReader = null;
+
     @NonNull
     @Override
     public ProcessingNode.In transform(@NonNull In inputEdge) {
@@ -100,6 +110,7 @@
         // Create and configure ImageReader.
         Consumer<ProcessingRequest> requestConsumer;
         ImageReaderProxy wrappedImageReader;
+        ImageReaderProxy secondaryWrappedImageReader = null;
         boolean hasMetadata = !inputEdge.isVirtualCamera();
         CameraCaptureCallback progressCallback = new CameraCaptureCallback() {
             @Override
@@ -120,15 +131,36 @@
                 });
             }
         };
+
         CameraCaptureCallback cameraCaptureCallbacks;
+        CameraCaptureCallback secondaryCameraCaptureCallback = null;
+        boolean isSimultaneousCaptureEnabled = inputEdge.getOutputFormats().size() > 1;
         if (hasMetadata && inputEdge.getImageReaderProxyProvider() == null) {
-            // Use MetadataImageReader if the input edge expects metadata.
-            MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
-                    size.getHeight(), format, MAX_IMAGES);
-            cameraCaptureCallbacks =
-                    CameraCaptureCallbacks.createComboCallback(
-                            progressCallback, metadataImageReader.getCameraCaptureCallback());
-            wrappedImageReader = metadataImageReader;
+            if (isSimultaneousCaptureEnabled) {
+                MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
+                        size.getHeight(), JPEG, MAX_IMAGES);
+                cameraCaptureCallbacks =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback, metadataImageReader.getCameraCaptureCallback());
+                wrappedImageReader = metadataImageReader;
+
+                MetadataImageReader secondaryMetadataImageReader = new MetadataImageReader(
+                        size.getWidth(), size.getHeight(), RAW_SENSOR, MAX_IMAGES);
+                secondaryCameraCaptureCallback =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback,
+                                secondaryMetadataImageReader.getCameraCaptureCallback());
+                secondaryWrappedImageReader = secondaryMetadataImageReader;
+            } else {
+                // Use MetadataImageReader if the input edge expects metadata.
+                MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
+                        size.getHeight(), format, MAX_IMAGES);
+                cameraCaptureCallbacks =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback, metadataImageReader.getCameraCaptureCallback());
+                wrappedImageReader = metadataImageReader;
+            }
+
             requestConsumer = this::onRequestAvailable;
         } else {
             cameraCaptureCallbacks = progressCallback;
@@ -144,33 +176,14 @@
             };
         }
         inputEdge.setCameraCaptureCallback(cameraCaptureCallbacks);
+        if (isSimultaneousCaptureEnabled && secondaryCameraCaptureCallback != null) {
+            inputEdge.setSecondaryCameraCaptureCallback(secondaryCameraCaptureCallback);
+        }
         inputEdge.setSurface(requireNonNull(wrappedImageReader.getSurface()));
         mSafeCloseImageReaderProxy = new SafeCloseImageReaderProxy(wrappedImageReader);
 
         // Listen to the input edges.
-        wrappedImageReader.setOnImageAvailableListener(imageReader -> {
-            try {
-                ImageProxy image = imageReader.acquireLatestImage();
-                if (image != null) {
-                    onImageProxyAvailable(image);
-                } else {
-                    if (mCurrentRequest != null) {
-                        sendCaptureError(
-                                TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
-                                        new ImageCaptureException(ERROR_CAPTURE_FAILED,
-                                                "Failed to acquire latest image", null)));
-                    }
-                }
-            } catch (IllegalStateException e) {
-                if (mCurrentRequest != null) {
-                    sendCaptureError(
-                            TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
-                                    new ImageCaptureException(
-                                            ERROR_CAPTURE_FAILED, "Failed to acquire latest image",
-                                            e)));
-                }
-            }
-        }, mainThreadExecutor());
+        setOnImageAvailableListener(wrappedImageReader);
 
         // Postview
         if (inputEdge.getPostviewSize() != null) {
@@ -196,10 +209,20 @@
                     inputEdge.getPostviewSize(), inputEdge.getPostviewImageFormat());
         }
 
+        // Simultaneous capture RAW + JPEG
+        if (isSimultaneousCaptureEnabled && secondaryWrappedImageReader != null) {
+            inputEdge.setSecondarySurface(secondaryWrappedImageReader.getSurface());
+            mSecondarySafeCloseImageReaderProxy = new SafeCloseImageReaderProxy(
+                    secondaryWrappedImageReader);
+            setOnImageAvailableListener(secondaryWrappedImageReader);
+        }
+
         inputEdge.getRequestEdge().setListener(requestConsumer);
         inputEdge.getErrorEdge().setListener(this::sendCaptureError);
 
-        mOutputEdge = ProcessingNode.In.of(inputEdge.getInputFormat(), inputEdge.getOutputFormat());
+        mOutputEdge = ProcessingNode.In.of(
+                inputEdge.getInputFormat(),
+                inputEdge.getOutputFormats());
 
         return mOutputEdge;
     }
@@ -215,6 +238,33 @@
         }
     }
 
+    private void setOnImageAvailableListener(@NonNull ImageReaderProxy imageReaderProxy) {
+        imageReaderProxy.setOnImageAvailableListener(imageReader -> {
+            try {
+                ImageProxy image = imageReader.acquireLatestImage();
+                if (image != null) {
+                    onImageProxyAvailable(image);
+                } else {
+                    if (mCurrentRequest != null) {
+                        sendCaptureError(
+                                TakePictureManager.CaptureError.of(
+                                        mCurrentRequest.getRequestId(),
+                                        new ImageCaptureException(ERROR_CAPTURE_FAILED,
+                                                "Failed to acquire latest image", null)));
+                    }
+                }
+            } catch (IllegalStateException e) {
+                if (mCurrentRequest != null) {
+                    sendCaptureError(
+                            TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
+                                    new ImageCaptureException(
+                                            ERROR_CAPTURE_FAILED,
+                                            "Failed to acquire latest image", e)));
+                }
+            }
+        }, mainThreadExecutor());
+    }
+
     @VisibleForTesting
     @MainThread
     void onImageProxyAvailable(@NonNull ImageProxy imageProxy) {
@@ -247,7 +297,22 @@
 
         // The capture is complete. Let the pipeline know it can take another picture.
         ProcessingRequest request = mCurrentRequest;
-        mCurrentRequest = null;
+
+        // If simultaneous capture RAW + JPEG, only reset when both images are processed.
+        boolean isSimultaneousCaptureEnabled = mInputEdge != null
+                && mInputEdge.getOutputFormats().size() > 1;
+        if (isSimultaneousCaptureEnabled && mCurrentRequest != null) {
+            mCurrentRequest.getTakePictureRequest()
+                    .markFormatProcessStatusInSimultaneousCapture(
+                            imageProxy.getFormat(), true);
+        }
+        boolean isProcessed =
+                !isSimultaneousCaptureEnabled || (mCurrentRequest != null
+                        && mCurrentRequest.getTakePictureRequest()
+                        .isFormatProcessedInSimultaneousCapture());
+        if (isProcessed) {
+            mCurrentRequest = null;
+        }
         request.onImageCaptured();
     }
 
@@ -309,12 +374,14 @@
         checkMainThread();
         releaseInputResources(requireNonNull(mInputEdge),
                 requireNonNull(mSafeCloseImageReaderProxy),
+                mSecondarySafeCloseImageReaderProxy,
                 mSafeCloseImageReaderForPostview);
 
     }
 
     private void releaseInputResources(@NonNull CaptureNode.In inputEdge,
             @NonNull SafeCloseImageReaderProxy imageReader,
+            @Nullable SafeCloseImageReaderProxy secondaryImageReader,
             @Nullable SafeCloseImageReaderProxy imageReaderForPostview) {
         inputEdge.getSurface().close();
         // Wait for the termination to close the ImageReader or the Surface may be released
@@ -331,6 +398,15 @@
                 }
             }, mainThreadExecutor());
         }
+
+        if (inputEdge.getOutputFormats().size() > 1 && inputEdge.getSecondarySurface() != null) {
+            inputEdge.getSecondarySurface().close();
+            inputEdge.getSecondarySurface().getTerminationFuture().addListener(() -> {
+                if (secondaryImageReader != null) {
+                    secondaryImageReader.safeClose();
+                }
+            }, mainThreadExecutor());
+        }
     }
 
     @VisibleForTesting
@@ -371,15 +447,24 @@
         private CameraCaptureCallback mCameraCaptureCallback = new CameraCaptureCallback() {
         };
 
+        /* Additional camera capture callback for simultaneous RAW + JPEG capture */
+        @Nullable
+        private CameraCaptureCallback mSecondaryCameraCaptureCallback;
+
         @Nullable
         private DeferrableSurface mSurface;
 
+        /* Additional surface for simultaneous RAW + JPEG capture */
+        @Nullable
+        private DeferrableSurface mSecondarySurface;
+
         @Nullable
         private DeferrableSurface mPostviewSurface = null;
 
         /**
          * Size of the {@link ImageReader} buffer.
          */
+        @NonNull
         abstract Size getSize();
 
         /**
@@ -390,10 +475,13 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
-         * supported. Other formats are only used by in-memory capture in tests.
+         * <p> For public users, {@link ImageFormat#JPEG}, {@link ImageFormat#JPEG_R} and
+         * {@link ImageFormat#RAW_SENSOR}} are supported. Other formats are only used by in-memory
+         * capture in tests.
          */
-        abstract int getOutputFormat();
+        @SuppressWarnings("AutoValueImmutableFields")
+        @NonNull
+        abstract List<Integer> getOutputFormats();
 
         /**
          * Whether the pipeline is connected to a virtual camera.
@@ -449,6 +537,13 @@
             return mPostviewSurface;
         }
 
+        /**
+         * Edge that accepts the image frames for simultaneous RAW + JPEG capture.
+         */
+        @Nullable
+        DeferrableSurface getSecondarySurface() {
+            return mSecondarySurface;
+        }
 
         void setSurface(@NonNull Surface surface) {
             checkState(mSurface == null, "The surface is already set.");
@@ -459,6 +554,12 @@
             mPostviewSurface = new ImmediateSurface(surface, size, imageFormat);
         }
 
+        void setSecondarySurface(@NonNull Surface surface) {
+            checkState(mSecondarySurface == null, "The secondary surface is "
+                    + "already set.");
+            mSecondarySurface = new ImmediateSurface(surface, getSize(), getInputFormat());
+        }
+
         /**
          * Edge that accepts image metadata.
          *
@@ -473,19 +574,37 @@
             mCameraCaptureCallback = cameraCaptureCallback;
         }
 
+        @Nullable
+        CameraCaptureCallback getSecondaryCameraCaptureCallback() {
+            return mSecondaryCameraCaptureCallback;
+        }
+
+        void setSecondaryCameraCaptureCallback(
+                @NonNull CameraCaptureCallback cameraCaptureCallback) {
+            mSecondaryCameraCaptureCallback = cameraCaptureCallback;
+        }
+
         @NonNull
-        static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
+        static In of(
+                @NonNull Size size,
+                int inputFormat,
+                @NonNull List<Integer> outputFormats,
+                boolean isVirtualCamera,
                 @Nullable ImageReaderProxyProvider imageReaderProxyProvider) {
-            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
+            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormats, isVirtualCamera,
                     imageReaderProxyProvider, null, ImageFormat.YUV_420_888,
                     new Edge<>(), new Edge<>());
         }
 
         @NonNull
-        static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
+        static In of(
+                @NonNull Size size,
+                int inputFormat,
+                @NonNull List<Integer> outputFormats,
+                boolean isVirtualCamera,
                 @Nullable ImageReaderProxyProvider imageReaderProxyProvider,
                 @Nullable Size postviewSize, int postviewImageFormat) {
-            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
+            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormats, isVirtualCamera,
                     imageReaderProxyProvider, postviewSize, postviewImageFormat,
                     new Edge<>(), new Edge<>());
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
index 812be3d..124049a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
@@ -126,11 +126,20 @@
                 cameraCharacteristics,
                 cameraEffect != null ? new InternalImageProcessor(cameraEffect) : null);
 
+        // Pass down [RAW_SENSOR, JPEG] to the pipeline if simultaneous capture is enabled.
+        List<Integer> outputFormats = new ArrayList<>();
+        if (mUseCaseConfig.getSecondaryInputFormat() != ImageFormat.UNKNOWN) {
+            outputFormats.add(ImageFormat.RAW_SENSOR);
+            outputFormats.add(ImageFormat.JPEG);
+        } else {
+            outputFormats.add(getOutputFormat());
+        }
+
         // Connect nodes
         mPipelineIn = CaptureNode.In.of(
                 cameraSurfaceSize,
                 mUseCaseConfig.getInputFormat(),
-                getOutputFormat(),
+                outputFormats,
                 isVirtualCamera,
                 mUseCaseConfig.getImageReaderProxyProvider(),
                 postviewSize,
@@ -147,6 +156,10 @@
         SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mUseCaseConfig,
                 resolution);
         builder.addNonRepeatingSurface(mPipelineIn.getSurface());
+        if (mPipelineIn.getOutputFormats().size() > 1
+                && mPipelineIn.getSecondarySurface() != null) {
+            builder.addNonRepeatingSurface(mPipelineIn.getSecondarySurface());
+        }
 
         // Postview surface is generated when initializing CaptureNode.
         if (mPipelineIn.getPostviewSurface() != null) {
@@ -274,11 +287,7 @@
             @NonNull ListenableFuture<Void> captureFuture) {
         return new ProcessingRequest(
                 captureBundle,
-                takePictureRequest.getOutputFileOptions(),
-                takePictureRequest.getCropRect(),
-                takePictureRequest.getRotationDegrees(),
-                takePictureRequest.getJpegQuality(),
-                takePictureRequest.getSensorToBufferTransform(),
+                takePictureRequest,
                 takePictureCallback,
                 captureFuture,
                 requestId);
@@ -310,6 +319,10 @@
             builder.addAllCameraCaptureCallbacks(
                     takePictureRequest.getSessionConfigCameraCaptureCallbacks());
             builder.addSurface(mPipelineIn.getSurface());
+            if (mPipelineIn.getOutputFormats().size() > 1
+                    && mPipelineIn.getSecondarySurface() != null) {
+                builder.addSurface(mPipelineIn.getSecondarySurface());
+            }
             builder.setPostviewEnabled(shouldEnablePostview());
 
             // Sets the JPEG rotation and quality for JPEG and RAW formats. Some devices do not
@@ -332,6 +345,10 @@
             builder.addTag(tagBundleKey, captureStage.getId());
             builder.setId(requestId);
             builder.addCameraCaptureCallback(mPipelineIn.getCameraCaptureCallback());
+            if (mPipelineIn.getOutputFormats().size() > 1
+                    && mPipelineIn.getSecondaryCameraCaptureCallback() != null) {
+                builder.addCameraCaptureCallback(mPipelineIn.getSecondaryCameraCaptureCallback());
+            }
             captureConfigs.add(builder.build());
         }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
index 26085ba..6e32225 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.internal.compat.workaround.InvalidJpegDataParser;
@@ -40,7 +41,9 @@
 /**
  * Saves JPEG bytes to disk.
  */
-class JpegBytes2Disk implements Operation<JpegBytes2Disk.In, ImageCapture.OutputFileResults> {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class JpegBytes2Disk implements
+        Operation<JpegBytes2Disk.In, ImageCapture.OutputFileResults> {
 
     @NonNull
     @Override
@@ -72,7 +75,8 @@
      * Input packet.
      */
     @AutoValue
-    abstract static class In {
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public abstract static class In {
 
         @NonNull
         abstract Packet<byte[]> getPacket();
@@ -81,7 +85,7 @@
         abstract ImageCapture.OutputFileOptions getOutputFileOptions();
 
         @NonNull
-        static In of(@NonNull Packet<byte[]> jpegBytes,
+        public static In of(@NonNull Packet<byte[]> jpegBytes,
                 @NonNull ImageCapture.OutputFileOptions outputFileOptions) {
             return new AutoValue_JpegBytes2Disk_In(jpegBytes, outputFileOptions);
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
index fddd9b7..5819ecd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
@@ -54,6 +54,7 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -72,6 +73,10 @@
     @Nullable
     private final CameraCharacteristics mCameraCharacteristics;
 
+    @VisibleForTesting
+    @Nullable
+    DngImage2Disk mDngImage2Disk;
+
     private ProcessingNode.In mInputEdge;
     private Operation<InputPacket, Packet<ImageProxy>> mInput2Packet;
     private Operation<Image2JpegBytes.In, Packet<byte[]>> mImage2JpegBytes;
@@ -189,12 +194,20 @@
     void processInputPacket(@NonNull InputPacket inputPacket) {
         ProcessingRequest request = inputPacket.getProcessingRequest();
         try {
+            // If simultaneous capture RAW + JPEG, only trigger callback when both images
+            // are available and processed.
+            boolean isSimultaneousCaptureEnabled = mInputEdge.getOutputFormats().size() > 1;
             if (inputPacket.getProcessingRequest().isInMemoryCapture()) {
                 ImageProxy result = processInMemoryCapture(inputPacket);
                 mainThreadExecutor().execute(() -> request.onFinalResult(result));
             } else {
                 ImageCapture.OutputFileResults result = processOnDiskCapture(inputPacket);
-                mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                boolean isProcessed =
+                        !isSimultaneousCaptureEnabled || request.getTakePictureRequest()
+                                .isFormatProcessedInSimultaneousCapture();
+                if (isProcessed) {
+                    mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                }
             }
         } catch (ImageCaptureException e) {
             sendError(request, e);
@@ -209,7 +222,10 @@
 
     @WorkerThread
     void processPostviewInputPacket(@NonNull InputPacket inputPacket) {
-        int format = mInputEdge.getOutputFormat();
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+
+        int format = outputFormats.get(0);
         checkArgument(format == YUV_420_888 || format == JPEG,
                 String.format("Postview only support YUV and JPEG output formats. "
                         + "Output format: %s", format));
@@ -228,37 +244,97 @@
     @WorkerThread
     ImageCapture.OutputFileResults processOnDiskCapture(@NonNull InputPacket inputPacket)
             throws ImageCaptureException {
-        int format = mInputEdge.getOutputFormat();
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+        int format = outputFormats.get(0);
         checkArgument(isJpegFormats(format)
                 || isRawFormats(format),
                 String.format("On-disk capture only support JPEG and"
                 + " JPEG/R and RAW output formats. Output format: %s", format));
         ProcessingRequest request = inputPacket.getProcessingRequest();
+        checkArgument(request.getOutputFileOptions() != null
+                && !request.getOutputFileOptions().isEmpty(),
+                "OutputFileOptions cannot be empty");
         Packet<ImageProxy> originalImage = mInput2Packet.apply(inputPacket);
-
-        switch (format) {
-            case RAW_SENSOR:
-                DngImage2Disk dngImage2Disk = new DngImage2Disk(
-                        requireNonNull(mCameraCharacteristics),
-                        originalImage.getCameraCaptureResult().getCaptureResult());
-                return dngImage2Disk.apply(DngImage2Disk.In.of(
-                        originalImage.getData(),
-                        originalImage.getRotationDegrees(),
-                        requireNonNull(request.getOutputFileOptions())));
-            case JPEG:
-            default:
-                Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
-                        Image2JpegBytes.In.of(originalImage, request.getJpegQuality()));
-                if (jpegBytes.hasCropping() || mBitmapEffect != null) {
-                    jpegBytes = cropAndMaybeApplyEffect(jpegBytes, request.getJpegQuality());
-                }
-                return mJpegBytes2Disk.apply(
-                        JpegBytes2Disk.In.of(jpegBytes,
-                                requireNonNull(request.getOutputFileOptions())));
+        boolean isSimultaneousCaptureEnabled = outputFormats.size() > 1;
+        if (isSimultaneousCaptureEnabled) {
+            // If simultaneous capture RAW + JPEG, use the first output file options for JPEG and
+            // the second for RAW.
+            checkArgument(request.getOutputFileOptions() != null
+                            && request.getOutputFileOptions().size() > 1,
+                    "The number of OutputFileOptions for simultaneous capture "
+                            + "should be at least two");
+            ImageCapture.OutputFileResults outputFileResults = null;
+            switch (originalImage.getFormat()) {
+                case RAW_SENSOR:
+                    outputFileResults = saveRawToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0));
+                    request.getTakePictureRequest()
+                            .markFormatProcessStatusInSimultaneousCapture(RAW_SENSOR, true);
+                    return outputFileResults;
+                case JPEG:
+                default:
+                    outputFileResults = saveJpegToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(1),
+                            request.getJpegQuality());
+                    request.getTakePictureRequest()
+                            .markFormatProcessStatusInSimultaneousCapture(JPEG, true);
+                    return outputFileResults;
+            }
+        } else {
+            switch (format) {
+                case RAW_SENSOR:
+                    return saveRawToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0));
+                case JPEG:
+                default:
+                    return saveJpegToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0),
+                            request.getJpegQuality());
+            }
         }
     }
 
     @NonNull
+    private ImageCapture.OutputFileResults saveRawToDisk(
+            @NonNull Packet<ImageProxy> originalImage,
+            @NonNull ImageCapture.OutputFileOptions outputFileOptions)
+            throws ImageCaptureException {
+
+        if (mDngImage2Disk == null) {
+            if (mCameraCharacteristics == null) {
+                throw new ImageCaptureException(ERROR_UNKNOWN,
+                        "CameraCharacteristics is null, DngCreator cannot be created", null);
+            }
+            if (originalImage.getCameraCaptureResult().getCaptureResult() == null) {
+                throw new ImageCaptureException(ERROR_UNKNOWN,
+                        "CameraCaptureResult is null, DngCreator cannot be created", null);
+            }
+            mDngImage2Disk = new DngImage2Disk(
+                    requireNonNull(mCameraCharacteristics),
+                    requireNonNull(originalImage.getCameraCaptureResult().getCaptureResult()));
+        }
+        return mDngImage2Disk.apply(DngImage2Disk.In.of(
+                originalImage.getData(),
+                originalImage.getRotationDegrees(),
+                requireNonNull(outputFileOptions)));
+    }
+
+    @NonNull
+    private ImageCapture.OutputFileResults saveJpegToDisk(
+            @NonNull Packet<ImageProxy> originalImage,
+            @NonNull ImageCapture.OutputFileOptions outputFileOptions,
+            int jpegQuality) throws ImageCaptureException {
+        Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
+                Image2JpegBytes.In.of(originalImage, jpegQuality));
+        if (jpegBytes.hasCropping() || mBitmapEffect != null) {
+            jpegBytes = cropAndMaybeApplyEffect(jpegBytes, jpegQuality);
+        }
+        return mJpegBytes2Disk.apply(
+                JpegBytes2Disk.In.of(jpegBytes, requireNonNull(outputFileOptions)));
+    }
+
+    @NonNull
     @WorkerThread
     ImageProxy processInMemoryCapture(@NonNull InputPacket inputPacket)
             throws ImageCaptureException {
@@ -266,9 +342,12 @@
         Packet<ImageProxy> image = mInput2Packet.apply(inputPacket);
         // TODO(b/322311893): Update to handle JPEG/R as output format in the if-statement when YUV
         //  to JPEG/R and effect with JPEG/R are supported.
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+        int format = outputFormats.get(0);
+
         if ((image.getFormat() == YUV_420_888 || mBitmapEffect != null
-                || mHasIncorrectJpegMetadataQuirk)
-                && mInputEdge.getOutputFormat() == JPEG) {
+                || mHasIncorrectJpegMetadataQuirk) && (format == JPEG)) {
             Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
                     Image2JpegBytes.In.of(image, request.getJpegQuality()));
             if (mBitmapEffect != null) {
@@ -276,7 +355,13 @@
             }
             image = mJpegBytes2Image.apply(jpegBytes);
         }
-        return mJpegImage2Result.apply(image);
+        ImageProxy imageProxy = mJpegImage2Result.apply(image);
+        boolean isSimultaneousCaptureEnabled = outputFormats.size() > 1;
+        if (isSimultaneousCaptureEnabled) {
+            request.getTakePictureRequest()
+                    .markFormatProcessStatusInSimultaneousCapture(imageProxy.getFormat(), true);
+        }
+        return imageProxy;
     }
 
     /**
@@ -297,9 +382,11 @@
     /**
      * Sends {@link ImageCaptureException} to {@link TakePictureManager}.
      */
-    private static void sendError(@NonNull ProcessingRequest request,
+    private void sendError(@NonNull ProcessingRequest request,
             @NonNull ImageCaptureException e) {
-        mainThreadExecutor().execute(() -> request.onProcessFailure(e));
+        mainThreadExecutor().execute(() -> {
+            request.onProcessFailure(e);
+        });
     }
 
     /**
@@ -329,12 +416,14 @@
         /**
          * Get the main input edge that contains a {@link InputPacket} flow.
          */
+        @NonNull
         abstract Edge<InputPacket> getEdge();
 
 
         /**
          * Get the postview input edge.
          */
+        @NonNull
         abstract Edge<InputPacket> getPostviewEdge();
 
         /**
@@ -345,14 +434,18 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
-         * supported. Other formats are only used by in-memory capture in tests.
+         * <p> For public users, {@link ImageFormat#JPEG}, {@link ImageFormat#JPEG_R} and
+         * {@link ImageFormat#RAW_SENSOR}} are supported. Other formats are only used by in-memory
+         * capture in tests.
          */
-        abstract int getOutputFormat();
+        @SuppressWarnings("AutoValueImmutableFields")
+        @NonNull
+        abstract List<Integer> getOutputFormats();
 
-        static In of(int inputFormat, int outputFormat) {
+        static In of(int inputFormat,
+                @NonNull List<Integer> outputFormats) {
             return new AutoValue_ProcessingNode_In(new Edge<>(), new Edge<>(),
-                    inputFormat, outputFormat);
+                    inputFormat, outputFormats);
         }
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
index 35a7fc0..bf70e64 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
@@ -41,8 +41,9 @@
  */
 class ProcessingRequest {
     private final int mRequestId;
+    @NonNull TakePictureRequest mTakePictureRequest;
     @Nullable
-    private final ImageCapture.OutputFileOptions mOutputFileOptions;
+    private final List<ImageCapture.OutputFileOptions> mOutputFileOptions;
     @NonNull
     private final Rect mCropRect;
     private final int mRotationDegrees;
@@ -62,32 +63,24 @@
 
     ProcessingRequest(
             @NonNull CaptureBundle captureBundle,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Rect cropRect,
-            int rotationDegrees,
-            int jpegQuality,
-            @NonNull Matrix sensorToBufferTransform,
+            @NonNull TakePictureRequest takePictureRequest,
             @NonNull TakePictureCallback callback,
             @NonNull ListenableFuture<Void> captureFuture) {
-        this(captureBundle, outputFileOptions, cropRect, rotationDegrees, jpegQuality,
-                sensorToBufferTransform, callback, captureFuture, 0);
+        this(captureBundle, takePictureRequest, callback, captureFuture, 0);
     }
     ProcessingRequest(
             @NonNull CaptureBundle captureBundle,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Rect cropRect,
-            int rotationDegrees,
-            int jpegQuality,
-            @NonNull Matrix sensorToBufferTransform,
+            @NonNull TakePictureRequest takePictureRequest,
             @NonNull TakePictureCallback callback,
             @NonNull ListenableFuture<Void> captureFuture,
             int requestId) {
         mRequestId = requestId;
-        mOutputFileOptions = outputFileOptions;
-        mJpegQuality = jpegQuality;
-        mRotationDegrees = rotationDegrees;
-        mCropRect = cropRect;
-        mSensorToBufferTransform = sensorToBufferTransform;
+        mTakePictureRequest = takePictureRequest;
+        mOutputFileOptions = takePictureRequest.getOutputFileOptions();
+        mJpegQuality = takePictureRequest.getJpegQuality();
+        mRotationDegrees = takePictureRequest.getRotationDegrees();
+        mCropRect = takePictureRequest.getCropRect();
+        mSensorToBufferTransform = takePictureRequest.getSensorToBufferTransform();
         mCallback = callback;
         mTagBundleKey = String.valueOf(captureBundle.hashCode());
         mStageIds = new ArrayList<>();
@@ -111,8 +104,13 @@
         return mRequestId;
     }
 
+    @NonNull
+    TakePictureRequest getTakePictureRequest() {
+        return mTakePictureRequest;
+    }
+
     @Nullable
-    ImageCapture.OutputFileOptions getOutputFileOptions() {
+    List<ImageCapture.OutputFileOptions> getOutputFileOptions() {
         return mOutputFileOptions;
     }
 
@@ -135,7 +133,7 @@
     }
 
     boolean isInMemoryCapture() {
-        return getOutputFileOptions() == null;
+        return getOutputFileOptions() == null || getOutputFileOptions().isEmpty();
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
index 61dc018..a0faca3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
@@ -49,6 +49,7 @@
     private final ListenableFuture<Void> mCompleteFuture;
     private CallbackToFutureAdapter.Completer<Void> mCaptureCompleter;
     private CallbackToFutureAdapter.Completer<Void> mCompleteCompleter;
+
     // Flag tracks if the request has been aborted by the UseCase. Once aborted, this class stops
     // propagating callbacks to the app.
     private boolean mIsAborted = false;
@@ -282,7 +283,13 @@
     }
 
     private void markComplete() {
-        checkState(!mCompleteFuture.isDone(), "The callback can only complete once.");
+        if (mTakePictureRequest.isSimultaneousCapture()
+                && !mTakePictureRequest.isFormatProcessedInSimultaneousCapture()) {
+            return;
+        }
+        if (!mTakePictureRequest.isSimultaneousCapture()) {
+            checkState(!mCompleteFuture.isDone(), "The callback can only complete once.");
+        }
         mCompleteCompleter.set(null);
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
index 4ef3f3b..120b23c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.core.imagecapture;
 
+import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.RAW_SENSOR;
+
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.core.util.Preconditions.checkArgument;
 
@@ -33,6 +36,7 @@
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Logger;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.ImageOutputConfig;
@@ -41,7 +45,9 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 /**
@@ -53,6 +59,8 @@
 @AutoValue
 public abstract class TakePictureRequest {
 
+    private static final String TAG = "TakePictureRequest";
+
     /**
      * By default, ImageCapture does not retry requests. For some problematic devices, the
      * capture request can become success after retrying. The allowed retry count will be
@@ -61,6 +69,17 @@
     private int mRemainingRetires = new CaptureFailedRetryEnabler().getRetryCount();
 
     /**
+     * Map to track image capture status for simultaneous capture.
+     *
+     * <p>For RAW + JPEG, we will only have two formats
+     * {@link android.graphics.ImageFormat#RAW_SENSOR} and {@link android.graphics.ImageFormat#JPEG}
+     * but it could extend to other formats in the future.
+     *
+     * It is not thread-safe.
+     */
+    private final Map<Integer, Boolean> mFormatCaptureStatus = new HashMap<>();
+
+    /**
      * Gets the callback {@link Executor} provided by the app.
      */
     @NonNull
@@ -82,14 +101,14 @@
      * Gets the app provided options for on-disk capture.
      */
     @Nullable
-    abstract ImageCapture.OutputFileOptions getOutputFileOptions();
+    public abstract List<ImageCapture.OutputFileOptions> getOutputFileOptions();
 
     /**
      * A snapshot of {@link ImageCapture#getViewPortCropRect()} when
      * {@link ImageCapture#takePicture} is called.
      */
     @NonNull
-    abstract Rect getCropRect();
+    public abstract Rect getCropRect();
 
     /**
      * A snapshot of {@link ImageCapture#getSensorToBufferTransformMatrix()} when
@@ -102,14 +121,14 @@
      * A snapshot of rotation degrees when {@link ImageCapture#takePicture} is called.
      */
     @ImageOutputConfig.RotationValue
-    abstract int getRotationDegrees();
+    public abstract int getRotationDegrees();
 
     /**
      * A snapshot of {@link ImageCaptureConfig#getJpegQuality()} when
      * {@link ImageCapture#takePicture} is called.
      */
     @IntRange(from = 1, to = 100)
-    abstract int getJpegQuality();
+    public abstract int getJpegQuality();
 
     /**
      * Gets the capture mode of the request.
@@ -122,6 +141,11 @@
     abstract int getCaptureMode();
 
     /**
+     * Checks if the request is for simultaneous capture. Currently only RAW + JPEG are supported.
+     */
+    abstract boolean isSimultaneousCapture();
+
+    /**
      * Gets the {@link CameraCaptureCallback}s set on the {@link SessionConfig}.
      *
      * <p>This is for calling back to Camera2InterOp. See: aosp/947197.
@@ -166,6 +190,36 @@
         return mRemainingRetires;
     }
 
+    void initFormatProcessStatusInSimultaneousCapture() {
+        mFormatCaptureStatus.put(RAW_SENSOR, false);
+        mFormatCaptureStatus.put(JPEG, false);
+    }
+
+    /**
+     * Marks the format as processed in simultaneous capture.
+     */
+    void markFormatProcessStatusInSimultaneousCapture(int format, boolean isProcessed) {
+        if (!mFormatCaptureStatus.containsKey(format)) {
+            Logger.e(TAG, "The format is not supported in simultaneous capture");
+            return;
+        }
+        mFormatCaptureStatus.put(format, isProcessed);
+    }
+
+    /**
+     * Checks if all the formats are processed in simultaneous capture.
+     */
+    boolean isFormatProcessedInSimultaneousCapture() {
+        boolean isProcessed = true;
+        for (Map.Entry<Integer, Boolean> entry : mFormatCaptureStatus.entrySet()) {
+            if (!entry.getValue()) {
+                isProcessed = false;
+                break;
+            }
+        }
+        return isProcessed;
+    }
+
     /**
      * Delivers {@link ImageCaptureException} to the app.
      */
@@ -229,20 +283,27 @@
     public static TakePictureRequest of(@NonNull Executor appExecutor,
             @Nullable ImageCapture.OnImageCapturedCallback inMemoryCallback,
             @Nullable ImageCapture.OnImageSavedCallback onDiskCallback,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
+            @Nullable List<ImageCapture.OutputFileOptions> outputFileOptions,
             @NonNull Rect cropRect,
             @NonNull Matrix sensorToBufferTransform,
             int rotationDegrees,
             int jpegQuality,
             @ImageCapture.CaptureMode int captureMode,
+            boolean isSimultaneousCapture,
             @NonNull List<CameraCaptureCallback> sessionConfigCameraCaptureCallbacks) {
         checkArgument((onDiskCallback == null) == (outputFileOptions == null),
                 "onDiskCallback and outputFileOptions should be both null or both non-null.");
         checkArgument((onDiskCallback == null) ^ (inMemoryCallback == null),
                 "One and only one on-disk or in-memory callback should be present.");
-        return new AutoValue_TakePictureRequest(appExecutor, inMemoryCallback,
-                onDiskCallback, outputFileOptions, cropRect, sensorToBufferTransform,
-                rotationDegrees, jpegQuality, captureMode, sessionConfigCameraCaptureCallbacks);
+        TakePictureRequest request = new AutoValue_TakePictureRequest(appExecutor, inMemoryCallback,
+                onDiskCallback, outputFileOptions, cropRect,
+                sensorToBufferTransform, rotationDegrees, jpegQuality, captureMode,
+                isSimultaneousCapture,
+                sessionConfigCameraCaptureCallbacks);
+        if (isSimultaneousCapture) {
+            request.initFormatProcessStatusInSimultaneousCapture();
+        }
+        return request;
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
index f0c7c00..7e5ca65 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core.impl;
 
+import android.graphics.ImageFormat;
+
 import androidx.annotation.NonNull;
 import androidx.camera.core.Camera;
 import androidx.camera.core.DynamicRange;
@@ -25,6 +27,8 @@
 public interface ImageInputConfig extends ReadableConfig {
     Config.Option<Integer> OPTION_INPUT_FORMAT =
             Config.Option.create("camerax.core.imageInput.inputFormat", int.class);
+    Config.Option<Integer> OPTION_SECONDARY_INPUT_FORMAT =
+            Config.Option.create("camerax.core.imageInput.secondaryInputFormat", int.class);
     Config.Option<DynamicRange> OPTION_INPUT_DYNAMIC_RANGE =
             Config.Option.create("camerax.core.imageInput.inputDynamicRange",
                     DynamicRange.class);
@@ -48,6 +52,19 @@
     }
 
     /**
+     * Retrieve the secondary input image format.
+     *
+     * <p>This is the format that is required for simultaneous capture. Currently only RAW + JPEG
+     * are supported and the input format must be set to RAW and secondary input format must be set
+     * to JPEG.
+     *
+     * <p>If the secondary input format is not set, {@link ImageFormat#UNKNOWN} will be returned.
+     */
+    default int getSecondaryInputFormat() {
+        return retrieveOption(OPTION_SECONDARY_INPUT_FORMAT, ImageFormat.UNKNOWN);
+    }
+
+    /**
      * Retrieve the required input {@link DynamicRange}.
      *
      * <p>This is the dynamic range that is required as input and it must be
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
index 816367e..80fac69 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.core.imagecapture
 
 import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.RAW_SENSOR
 import android.graphics.ImageFormat.YUV_420_888
 import android.os.Build
 import android.os.Looper.getMainLooper
@@ -57,7 +58,7 @@
 
     @Before
     fun setUp() {
-        captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null)
+        captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, null)
         captureNodeOut = captureNode.transform(captureNodeIn)
         captureNodeOut.edge.setListener { imagePropagated.add(it.imageProxy) }
     }
@@ -68,11 +69,47 @@
     }
 
     @Test
+    fun isNotSimultaneousCapture_createOneImageReaders() {
+        // Arrange: enable isSimultaneousCaptureEnabled in CaptureNode.In
+        val input = CaptureNode.In.of(Size(10, 10), RAW_SENSOR, listOf(RAW_SENSOR), false, null)
+
+        // Act: transform.
+        val node = CaptureNode()
+        val output = node.transform(input)
+
+        // Assert
+        assertThat(output.outputFormats.size).isEqualTo(1)
+        assertThat(output.outputFormats[0]).isEqualTo(RAW_SENSOR)
+        assertThat(input.surface).isNotNull()
+        assertThat(input.cameraCaptureCallback).isNotNull()
+        assertThat(input.secondarySurface).isNull()
+        assertThat(input.secondaryCameraCaptureCallback).isNull()
+    }
+
+    @Test
+    fun isSimultaneousCapture_createTwoImageReaders() {
+        // Arrange: enable isSimultaneousCaptureEnabled in CaptureNode.In
+        val input =
+            CaptureNode.In.of(Size(10, 10), RAW_SENSOR, listOf(RAW_SENSOR, JPEG), false, null)
+
+        // Act: transform.
+        val node = CaptureNode()
+        val output = node.transform(input)
+
+        // Assert
+        assertThat(output.outputFormats.size).isEqualTo(2)
+        assertThat(input.surface).isNotNull()
+        assertThat(input.cameraCaptureCallback).isNotNull()
+        assertThat(input.secondarySurface).isNotNull()
+        assertThat(input.secondaryCameraCaptureCallback).isNotNull()
+    }
+
+    @Test
     fun hasImageReaderProxyProvider_useTheProvidedImageReader() {
         // Arrange: create a fake ImageReaderProxyProvider.
         val imageReader = FakeImageReaderProxy(CaptureNode.MAX_IMAGES)
         val imageReaderProvider = ImageReaderProxyProvider { _, _, _, _, _ -> imageReader }
-        val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, imageReaderProvider)
+        val input = CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, imageReaderProvider)
         // Act: transform.
         val node = CaptureNode()
         node.transform(input)
@@ -114,7 +151,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -175,7 +212,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -206,7 +243,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -241,7 +278,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -280,7 +317,15 @@
         val postviewSize = Size(640, 480)
 
         val input =
-            CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null, postviewSize, YUV_420_888)
+            CaptureNode.In.of(
+                Size(10, 10),
+                JPEG,
+                listOf(JPEG),
+                false,
+                null,
+                postviewSize,
+                YUV_420_888
+            )
 
         // Act: transform.
         val node = CaptureNode()
@@ -298,7 +343,8 @@
         // Arrange: set the postviewSize to the CaptureNode.In
         val postviewSize = Size(640, 480)
 
-        val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null, postviewSize, JPEG)
+        val input =
+            CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, null, postviewSize, JPEG)
 
         // Act: transform.
         val node = CaptureNode()
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
index 658a6bb..7a40e02 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
@@ -20,6 +20,7 @@
 import android.graphics.Rect
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.CaptureBundle
 import androidx.concurrent.futures.CallbackToFutureAdapter
 import com.google.common.util.concurrent.ListenableFuture
@@ -27,6 +28,7 @@
 /** Fake [ProcessingRequest]. */
 internal class FakeProcessingRequest(
     outputFileOptions: ImageCapture.OutputFileOptions?,
+    secondaryOutputFileOptions: ImageCapture.OutputFileOptions?,
     captureBundle: CaptureBundle,
     cropRect: Rect,
     rotationDegrees: Int,
@@ -37,11 +39,15 @@
 ) :
     ProcessingRequest(
         captureBundle,
-        outputFileOptions,
-        cropRect,
-        rotationDegrees,
-        jpegQuality,
-        sensorToBufferTransform,
+        createTakePictureRequest(
+            if (outputFileOptions == null) null
+            else if (secondaryOutputFileOptions == null) listOf(outputFileOptions)
+            else listOf(outputFileOptions, secondaryOutputFileOptions),
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality
+        ),
         callback,
         captureFuture
     ) {
@@ -49,5 +55,5 @@
         captureBundle: CaptureBundle,
         callback: TakePictureCallback,
         captureFuture: ListenableFuture<Void> = CallbackToFutureAdapter.getFuture { "test" }
-    ) : this(null, captureBundle, CROP_RECT, 0, 100, Matrix(), callback, captureFuture)
+    ) : this(null, null, captureBundle, CROP_RECT, 0, 100, Matrix(), callback, captureFuture)
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
index 7b775e8..7bf9c75 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
@@ -35,6 +35,7 @@
     private var imageCapturedCallback: OnImageCapturedCallback? = null
     private var imageSavedCallback: OnImageSavedCallback? = null
     private var fileOptions: ImageCapture.OutputFileOptions? = null
+    private var secondaryFileOptions: ImageCapture.OutputFileOptions? = null
     var exceptionReceived: ImageCaptureException? = null
     var imageReceived: ImageProxy? = null
     var fileReceived: ImageCapture.OutputFileResults? = null
@@ -108,11 +109,11 @@
         imageSavedCallback = onDiskCallback
     }
 
-    override fun getOutputFileOptions(): ImageCapture.OutputFileOptions? {
-        return fileOptions
+    override fun getOutputFileOptions(): List<ImageCapture.OutputFileOptions> {
+        return listOfNotNull(fileOptions, secondaryFileOptions)
     }
 
-    internal override fun getCropRect(): Rect {
+    override fun getCropRect(): Rect {
         return Rect(0, 0, 640, 480)
     }
 
@@ -120,14 +121,18 @@
         return Matrix()
     }
 
-    internal override fun getRotationDegrees(): Int {
+    override fun getRotationDegrees(): Int {
         return ROTATION_DEGREES
     }
 
-    internal override fun getJpegQuality(): Int {
+    override fun getJpegQuality(): Int {
         return JPEG_QUALITY
     }
 
+    override fun isSimultaneousCapture(): Boolean {
+        return false
+    }
+
     internal override fun getCaptureMode(): Int {
         return ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
index 8ede6e5..f6a7c82 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
@@ -42,6 +42,7 @@
 import androidx.camera.core.imagecapture.Utils.JPEG_QUALITY
 import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SECONDARY_OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.SIZE
 import androidx.camera.core.imagecapture.Utils.WIDTH
@@ -66,6 +67,7 @@
 import androidx.camera.testing.impl.fakes.FakeImageReaderProxy
 import androidx.camera.testing.impl.fakes.GrayscaleImageEffect
 import androidx.core.util.Pair
+import com.google.common.collect.ImmutableList
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -198,7 +200,7 @@
     }
 
     @Test
-    fun createRequests_verifyCameraRequest_whenFormatIsRAw() {
+    fun createRequests_verifyCameraRequest_whenFormatIsRaw() {
         // Arrange.
         imageCaptureConfig = createImageCaptureConfig(inputFormat = ImageFormat.RAW_SENSOR)
         imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
@@ -213,6 +215,25 @@
     }
 
     @Test
+    fun createRequests_verifyCameraRequest_whenFormatIsRawAndSimultaneousCaptureEnabled() {
+        // Arrange.
+        imageCaptureConfig =
+            createImageCaptureConfig(
+                inputFormat = ImageFormat.RAW_SENSOR,
+                secondaryInputFormat = ImageFormat.JPEG
+            )
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
+        val captureInput = imagePipeline.captureNode.inputEdge
+
+        // Act: create requests
+        val result =
+            imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null))
+
+        // Assert: CameraRequest is constructed correctly.
+        verifyCaptureRequest(captureInput, result, true)
+    }
+
+    @Test
     fun createCameraRequestWithRotationQuirk_rotationNotInCaptureConfig() {
         // Arrange.
         injectRotationOptionQuirk()
@@ -352,12 +373,13 @@
 
                     override fun onError(exception: ImageCaptureException) {}
                 },
-                OUTPUT_FILE_OPTIONS,
+                ImmutableList.of(OUTPUT_FILE_OPTIONS, SECONDARY_OUTPUT_FILE_OPTIONS),
                 cropRect,
                 SENSOR_TO_BUFFER,
                 ROTATION_DEGREES,
                 JPEG_QUALITY,
                 captureMode,
+                false,
                 listOf()
             )
 
@@ -439,6 +461,22 @@
         assertThat(CALLBACK.inMemoryResult!!.planes).isEqualTo(image.planes)
     }
 
+    @Test
+    fun sendInMemoryRequest_receivesImageProxy_whenSimultaneousCaptureEnabled() {
+        // Arrange & act.
+        imageCaptureConfig =
+            createImageCaptureConfig(
+                inputFormat = ImageFormat.RAW_SENSOR,
+                secondaryInputFormat = ImageFormat.JPEG
+            )
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
+        val image = sendInMemoryRequest(imagePipeline)
+
+        // Assert: the image is received by TakePictureCallback.
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG)
+        assertThat(CALLBACK.inMemoryResult).isNotNull()
+    }
+
     /** Creates a ImageProxy and sends it to the pipeline. */
     private fun sendInMemoryRequest(
         pipeline: ImagePipeline,
@@ -525,6 +563,7 @@
     private fun createImageCaptureConfig(
         templateType: Int = TEMPLATE_TYPE,
         inputFormat: Int = ImageFormat.JPEG,
+        secondaryInputFormat: Int? = null,
     ): ImageCaptureConfig {
         val builder =
             ImageCapture.Builder().setCaptureOptionUnpacker { _, builder ->
@@ -532,6 +571,12 @@
             }
         builder.mutableConfig.insertOption(OPTION_IO_EXECUTOR, mainThreadExecutor())
         builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, inputFormat)
+        if (secondaryInputFormat != null) {
+            builder.mutableConfig.insertOption(
+                ImageInputConfig.OPTION_SECONDARY_INPUT_FORMAT,
+                secondaryInputFormat
+            )
+        }
         if (Build.VERSION.SDK_INT >= 34 && inputFormat == ImageFormat.JPEG_R) {
             builder.mutableConfig.insertOption(OPTION_INPUT_DYNAMIC_RANGE, HLG_10_BIT)
         }
@@ -541,13 +586,23 @@
 
     private fun verifyCaptureRequest(
         captureInput: CaptureNode.In,
-        result: Pair<CameraRequest, ProcessingRequest>
+        result: Pair<CameraRequest, ProcessingRequest>,
+        isSimultaneousCaptureEnabled: Boolean = false
     ) {
         val cameraRequest = result.first!!
         val captureConfig = cameraRequest.captureConfigs.single()
-        assertThat(captureConfig.cameraCaptureCallbacks)
-            .containsExactly(captureInput.cameraCaptureCallback)
-        assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
+        if (isSimultaneousCaptureEnabled) {
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .contains(captureInput.cameraCaptureCallback)
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .contains(captureInput.secondaryCameraCaptureCallback)
+            assertThat(captureConfig.surfaces).contains(captureInput.surface)
+            assertThat(captureConfig.surfaces).contains(captureInput.secondarySurface)
+        } else {
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .containsExactly(captureInput.cameraCaptureCallback)
+            assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
+        }
         assertThat(captureConfig.templateType).isEqualTo(TEMPLATE_TYPE)
         val jpegQuality = captureConfig.implementationOptions.retrieveOption(OPTION_JPEG_QUALITY)
         assertThat(jpegQuality).isEqualTo(JPEG_QUALITY)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
index 88695c5..d059a5a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
@@ -30,6 +30,7 @@
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.imagecapture.Utils.injectRotationOptionQuirk
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraCaptureResultImageInfo
@@ -137,11 +138,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                CROP_RECT,
-                90,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 90,
+                    /*jpegQuality=*/ 100
+                ),
                 FakeTakePictureCallback(),
                 Futures.immediateFuture(null)
             )
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
index b6ab8c8..7653d47 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
@@ -16,19 +16,23 @@
 
 package androidx.camera.core.imagecapture
 
-import android.graphics.ImageFormat
+import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.RAW_SENSOR
 import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
 import android.os.Build
 import android.os.Looper.getMainLooper
+import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.imagecapture.Utils.CAMERA_CAPTURE_RESULT
 import androidx.camera.core.imagecapture.Utils.HEIGHT
 import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SECONDARY_OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.isSequentialExecutor
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
@@ -38,13 +42,16 @@
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createRawFakeImageProxy
 import androidx.camera.testing.impl.fakes.FakeImageInfo
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.any
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
@@ -65,7 +72,7 @@
 
     @Before
     fun setUp() {
-        processingNodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        processingNodeIn = ProcessingNode.In.of(JPEG, listOf(JPEG))
         node.transform(processingNodeIn)
     }
 
@@ -76,11 +83,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -103,11 +112,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -123,6 +134,94 @@
     }
 
     @Test
+    fun processRequest_hasDiskResult_whenFormatIsRaw() {
+        // Arrange: create a request with callback.
+        processingNodeIn = ProcessingNode.In.of(RAW_SENSOR, listOf(RAW_SENSOR))
+        node.transform(processingNodeIn)
+
+        val callback = FakeTakePictureCallback()
+        val request =
+            ProcessingRequest(
+                { listOf() },
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
+                callback,
+                Futures.immediateFuture(null)
+            )
+
+        // Act: process the request.
+        val rawImage =
+            createRawFakeImageProxy(
+                CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+                WIDTH,
+                HEIGHT
+            )
+        val dngImage2Disk = mock(DngImage2Disk::class.java)
+        node.mDngImage2Disk = dngImage2Disk
+        `when`(dngImage2Disk.apply(any(DngImage2Disk.In::class.java)))
+            .thenReturn(mock(ImageCapture.OutputFileResults::class.java))
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, rawImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNotNull()
+    }
+
+    @Test
+    fun processRequest_hasDiskResult_whenSimultaneousCaptureEnabled() {
+        // Arrange: create a request with callback.
+        processingNodeIn = ProcessingNode.In.of(RAW_SENSOR, listOf(RAW_SENSOR, JPEG))
+        node.transform(processingNodeIn)
+
+        val callback = FakeTakePictureCallback()
+        val request =
+            ProcessingRequest(
+                { listOf() },
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS, SECONDARY_OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100,
+                    isSimultaneousCapture = true
+                ),
+                callback,
+                Futures.immediateFuture(null)
+            )
+
+        // Act: process the request.
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val jpegImage = createJpegFakeImageProxy(jpegBytes)
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, jpegImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNull()
+
+        // Act: process the request.
+        val rawImage =
+            createRawFakeImageProxy(
+                CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+                WIDTH,
+                HEIGHT
+            )
+        val dngImage2Disk = mock(DngImage2Disk::class.java)
+        node.mDngImage2Disk = dngImage2Disk
+        `when`(dngImage2Disk.apply(any(DngImage2Disk.In::class.java)))
+            .thenReturn(mock(ImageCapture.OutputFileResults::class.java))
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, rawImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNotNull()
+    }
+
+    @Test
     fun processAbortedRequest_noOps() {
         // Arrange: create a request with aborted callback.
         val callback = FakeTakePictureCallback()
@@ -130,11 +229,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -156,11 +257,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -183,11 +286,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -241,7 +346,7 @@
         // Creates the ProcessingNode after updating the device name to load the correct quirks
         node = ProcessingNode(mainThreadExecutor(), cameraCharacteristics)
 
-        processingNodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        processingNodeIn = ProcessingNode.In.of(JPEG, listOf(JPEG))
         node.transform(processingNodeIn)
 
         // Arrange: create an invalid ImageProxy.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
index 2a8bdd1..3305a82 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
@@ -30,13 +30,15 @@
 import androidx.camera.core.impl.ImageCaptureConfig
 import androidx.camera.core.impl.ImageInputConfig
 import androidx.camera.core.impl.TagBundle
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraCaptureResultImageInfo
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import androidx.camera.testing.impl.fakes.FakeCaptureStage
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import java.io.File
 import java.util.UUID
+import org.mockito.Mockito.mock
 import org.robolectric.util.ReflectionHelpers.setStaticField
 
 /** Utility methods for testing image capture. */
@@ -59,6 +61,8 @@
             it.deleteOnExit()
         }
     internal val OUTPUT_FILE_OPTIONS = ImageCapture.OutputFileOptions.Builder(TEMP_FILE).build()
+    internal val SECONDARY_OUTPUT_FILE_OPTIONS =
+        ImageCapture.OutputFileOptions.Builder(TEMP_FILE).build()
     internal val CAMERA_CAPTURE_RESULT = FakeCameraCaptureResult().also { it.timestamp = TIMESTAMP }
 
     internal fun createProcessingRequest(
@@ -67,11 +71,13 @@
     ): ProcessingRequest {
         return ProcessingRequest(
             captureBundle,
-            OUTPUT_FILE_OPTIONS,
-            CROP_RECT,
-            ROTATION_DEGREES,
-            /*jpegQuality=*/ 100,
-            SENSOR_TO_BUFFER,
+            createTakePictureRequest(
+                listOf(OUTPUT_FILE_OPTIONS),
+                CROP_RECT,
+                SENSOR_TO_BUFFER,
+                ROTATION_DEGREES,
+                JPEG_QUALITY
+            ),
             takePictureCallback,
             Futures.immediateFuture(null)
         )
@@ -101,8 +107,39 @@
     fun createCameraCaptureResultImageInfo(tagBundleKey: String, stageId: Int): ImageInfo {
         return CameraCaptureResultImageInfo(
             FakeCameraCaptureResult().also {
-                it.setTag(TagBundle.create(Pair(tagBundleKey, stageId)))
+                it.setTagBundle(TagBundle.create(Pair(tagBundleKey, stageId)))
             }
         )
     }
+
+    fun createTakePictureRequest(
+        outputFileOptions: List<ImageCapture.OutputFileOptions>?,
+        cropRect: Rect,
+        sensorToBufferTransform: Matrix,
+        rotationDegrees: Int,
+        jpegQuality: Int,
+        isSimultaneousCapture: Boolean = false
+    ): TakePictureRequest {
+        var onDiskCallback: ImageCapture.OnImageSavedCallback? = null
+        var onMemoryCallback: ImageCapture.OnImageCapturedCallback? = null
+        if (outputFileOptions == null) {
+            onMemoryCallback = mock(ImageCapture.OnImageCapturedCallback::class.java)
+        } else {
+            onDiskCallback = mock(ImageCapture.OnImageSavedCallback::class.java)
+        }
+
+        return TakePictureRequest.of(
+            CameraXExecutors.mainThreadExecutor(),
+            onMemoryCallback,
+            onDiskCallback,
+            outputFileOptions,
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality,
+            ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+            isSimultaneousCapture,
+            listOf()
+        )
+    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraCaptureResultImageInfoTest.java b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraCaptureResultImageInfoTest.java
index df3a7de..49518c6 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraCaptureResultImageInfoTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraCaptureResultImageInfoTest.java
@@ -21,7 +21,7 @@
 import android.os.Build;
 
 import androidx.camera.core.ImageInfo;
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
+import androidx.camera.testing.fakes.FakeCameraCaptureResult;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/PacketTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/PacketTest.kt
index dc83aae..af65491 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/PacketTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/PacketTest.kt
@@ -22,9 +22,9 @@
 import android.graphics.Rect
 import android.os.Build
 import android.util.Size
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import androidx.camera.testing.impl.ExifUtil.createExif
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
index f6211aa..f490f6c 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
@@ -66,8 +66,8 @@
 import androidx.camera.core.processing.DefaultSurfaceProcessor
 import androidx.camera.core.processing.SurfaceProcessorWithExecutor
 import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
 import androidx.camera.testing.impl.fakes.FakeSurfaceEffect
 import androidx.camera.testing.impl.fakes.FakeSurfaceProcessorInternal
 import androidx.camera.testing.impl.fakes.FakeUseCase
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraCaptureResultTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraCaptureResultTest.kt
index 0eb4320..1b17534 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraCaptureResultTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraCaptureResultTest.kt
@@ -20,7 +20,7 @@
 import android.util.Pair
 import androidx.camera.core.impl.CameraCaptureMetaData
 import androidx.camera.core.impl.TagBundle
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/camera/camera-testing/api/current.txt b/camera/camera-testing/api/current.txt
index fbf779a..a55b2f7 100644
--- a/camera/camera-testing/api/current.txt
+++ b/camera/camera-testing/api/current.txt
@@ -37,6 +37,40 @@
     method public void setHasTransform(boolean);
   }
 
+  public final class FakeCameraCaptureResult {
+    ctor public FakeCameraCaptureResult();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AeMode getAeMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AeState getAeState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AfMode getAfMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AfState getAfState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AwbMode getAwbMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AwbState getAwbState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.FlashState getFlashState();
+    method public androidx.camera.core.impl.TagBundle getTagBundle();
+    method public long getTimestamp();
+    method public void setAeMode(androidx.camera.core.impl.CameraCaptureMetaData.AeMode);
+    method public void setAeState(androidx.camera.core.impl.CameraCaptureMetaData.AeState);
+    method public void setAfMode(androidx.camera.core.impl.CameraCaptureMetaData.AfMode);
+    method public void setAfState(androidx.camera.core.impl.CameraCaptureMetaData.AfState);
+    method public void setAwbMode(androidx.camera.core.impl.CameraCaptureMetaData.AwbMode);
+    method public void setAwbState(androidx.camera.core.impl.CameraCaptureMetaData.AwbState);
+    method public void setFlashState(androidx.camera.core.impl.CameraCaptureMetaData.FlashState);
+    method public void setTagBundle(androidx.camera.core.impl.TagBundle);
+    method public void setTimestamp(long);
+  }
+
+  public static final class FakeCameraCaptureResult.Builder {
+    ctor public FakeCameraCaptureResult.Builder();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult build();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAeState(androidx.camera.core.impl.CameraCaptureMetaData.AeState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAfMode(androidx.camera.core.impl.CameraCaptureMetaData.AfMode?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAfState(androidx.camera.core.impl.CameraCaptureMetaData.AfState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAwbState(androidx.camera.core.impl.CameraCaptureMetaData.AwbState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setFlashState(androidx.camera.core.impl.CameraCaptureMetaData.FlashState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setTagBundle(androidx.camera.core.impl.TagBundle);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setTimestamp(long);
+  }
+
   public final class FakeCameraControl implements androidx.camera.core.CameraControl {
     ctor public FakeCameraControl();
     ctor public FakeCameraControl(androidx.camera.core.impl.CameraControlInternal.ControlUpdateCallback);
@@ -46,6 +80,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
     method public void clearInteropConfig();
     method public void clearNewCaptureRequestListener();
+    method public void completeAllCaptureRequests(androidx.camera.testing.imagecapture.CaptureResult);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
     method public int getExposureCompensationIndex();
     method public int getFlashMode();
@@ -59,9 +94,9 @@
     method public float getZoomRatio();
     method public boolean isZslConfigAdded();
     method public boolean isZslDisabledByByUserCaseConfig();
-    method public void notifyAllRequestsOnCaptureCancelled();
-    method public void notifyAllRequestsOnCaptureCompleted(androidx.camera.core.impl.CameraCaptureResult);
-    method public void notifyAllRequestsOnCaptureFailed();
+    method @Deprecated public void notifyAllRequestsOnCaptureCancelled();
+    method @Deprecated public void notifyAllRequestsOnCaptureCompleted(androidx.camera.core.impl.CameraCaptureResult);
+    method @Deprecated public void notifyAllRequestsOnCaptureFailed();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
     method public void setFlashMode(int);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(float);
@@ -71,6 +106,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
     method public void setZslDisabledByUserCaseConfig(boolean);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+    method public void submitCaptureResult(androidx.camera.testing.imagecapture.CaptureResult);
     method public com.google.common.util.concurrent.ListenableFuture<java.util.List<java.lang.Void!>!> submitStillCaptureRequests(java.util.List<androidx.camera.core.impl.CaptureConfig!>, int, int);
     field public static final androidx.camera.core.impl.CameraControlInternal DEFAULT_EMPTY_INSTANCE;
   }
@@ -122,3 +158,29 @@
 
 }
 
+package androidx.camera.testing.imagecapture {
+
+  public final class CaptureResult {
+    method public static androidx.camera.testing.imagecapture.CaptureResult cancelledResult();
+    method public static androidx.camera.testing.imagecapture.CaptureResult failedResult();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult? getCameraCaptureResult();
+    method public int getCaptureStatus();
+    method public static androidx.camera.testing.imagecapture.CaptureResult successfulResult();
+    method public static androidx.camera.testing.imagecapture.CaptureResult successfulResult(optional androidx.camera.testing.fakes.FakeCameraCaptureResult fakeCameraCaptureResult);
+    property public final androidx.camera.testing.fakes.FakeCameraCaptureResult? cameraCaptureResult;
+    property public final int captureStatus;
+    field public static final int CAPTURE_STATUS_CANCELLED = 2; // 0x2
+    field public static final int CAPTURE_STATUS_FAILED = 1; // 0x1
+    field public static final int CAPTURE_STATUS_SUCCESSFUL = 0; // 0x0
+    field public static final androidx.camera.testing.imagecapture.CaptureResult.Companion Companion;
+  }
+
+  public static final class CaptureResult.Companion {
+    method public androidx.camera.testing.imagecapture.CaptureResult cancelledResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult failedResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult successfulResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult successfulResult(optional androidx.camera.testing.fakes.FakeCameraCaptureResult fakeCameraCaptureResult);
+  }
+
+}
+
diff --git a/camera/camera-testing/api/restricted_current.txt b/camera/camera-testing/api/restricted_current.txt
index fbf779a..a55b2f7 100644
--- a/camera/camera-testing/api/restricted_current.txt
+++ b/camera/camera-testing/api/restricted_current.txt
@@ -37,6 +37,40 @@
     method public void setHasTransform(boolean);
   }
 
+  public final class FakeCameraCaptureResult {
+    ctor public FakeCameraCaptureResult();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AeMode getAeMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AeState getAeState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AfMode getAfMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AfState getAfState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AwbMode getAwbMode();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.AwbState getAwbState();
+    method public androidx.camera.core.impl.CameraCaptureMetaData.FlashState getFlashState();
+    method public androidx.camera.core.impl.TagBundle getTagBundle();
+    method public long getTimestamp();
+    method public void setAeMode(androidx.camera.core.impl.CameraCaptureMetaData.AeMode);
+    method public void setAeState(androidx.camera.core.impl.CameraCaptureMetaData.AeState);
+    method public void setAfMode(androidx.camera.core.impl.CameraCaptureMetaData.AfMode);
+    method public void setAfState(androidx.camera.core.impl.CameraCaptureMetaData.AfState);
+    method public void setAwbMode(androidx.camera.core.impl.CameraCaptureMetaData.AwbMode);
+    method public void setAwbState(androidx.camera.core.impl.CameraCaptureMetaData.AwbState);
+    method public void setFlashState(androidx.camera.core.impl.CameraCaptureMetaData.FlashState);
+    method public void setTagBundle(androidx.camera.core.impl.TagBundle);
+    method public void setTimestamp(long);
+  }
+
+  public static final class FakeCameraCaptureResult.Builder {
+    ctor public FakeCameraCaptureResult.Builder();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult build();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAeState(androidx.camera.core.impl.CameraCaptureMetaData.AeState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAfMode(androidx.camera.core.impl.CameraCaptureMetaData.AfMode?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAfState(androidx.camera.core.impl.CameraCaptureMetaData.AfState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setAwbState(androidx.camera.core.impl.CameraCaptureMetaData.AwbState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setFlashState(androidx.camera.core.impl.CameraCaptureMetaData.FlashState?);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setTagBundle(androidx.camera.core.impl.TagBundle);
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult.Builder setTimestamp(long);
+  }
+
   public final class FakeCameraControl implements androidx.camera.core.CameraControl {
     ctor public FakeCameraControl();
     ctor public FakeCameraControl(androidx.camera.core.impl.CameraControlInternal.ControlUpdateCallback);
@@ -46,6 +80,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
     method public void clearInteropConfig();
     method public void clearNewCaptureRequestListener();
+    method public void completeAllCaptureRequests(androidx.camera.testing.imagecapture.CaptureResult);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
     method public int getExposureCompensationIndex();
     method public int getFlashMode();
@@ -59,9 +94,9 @@
     method public float getZoomRatio();
     method public boolean isZslConfigAdded();
     method public boolean isZslDisabledByByUserCaseConfig();
-    method public void notifyAllRequestsOnCaptureCancelled();
-    method public void notifyAllRequestsOnCaptureCompleted(androidx.camera.core.impl.CameraCaptureResult);
-    method public void notifyAllRequestsOnCaptureFailed();
+    method @Deprecated public void notifyAllRequestsOnCaptureCancelled();
+    method @Deprecated public void notifyAllRequestsOnCaptureCompleted(androidx.camera.core.impl.CameraCaptureResult);
+    method @Deprecated public void notifyAllRequestsOnCaptureFailed();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
     method public void setFlashMode(int);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(float);
@@ -71,6 +106,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
     method public void setZslDisabledByUserCaseConfig(boolean);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+    method public void submitCaptureResult(androidx.camera.testing.imagecapture.CaptureResult);
     method public com.google.common.util.concurrent.ListenableFuture<java.util.List<java.lang.Void!>!> submitStillCaptureRequests(java.util.List<androidx.camera.core.impl.CaptureConfig!>, int, int);
     field public static final androidx.camera.core.impl.CameraControlInternal DEFAULT_EMPTY_INSTANCE;
   }
@@ -122,3 +158,29 @@
 
 }
 
+package androidx.camera.testing.imagecapture {
+
+  public final class CaptureResult {
+    method public static androidx.camera.testing.imagecapture.CaptureResult cancelledResult();
+    method public static androidx.camera.testing.imagecapture.CaptureResult failedResult();
+    method public androidx.camera.testing.fakes.FakeCameraCaptureResult? getCameraCaptureResult();
+    method public int getCaptureStatus();
+    method public static androidx.camera.testing.imagecapture.CaptureResult successfulResult();
+    method public static androidx.camera.testing.imagecapture.CaptureResult successfulResult(optional androidx.camera.testing.fakes.FakeCameraCaptureResult fakeCameraCaptureResult);
+    property public final androidx.camera.testing.fakes.FakeCameraCaptureResult? cameraCaptureResult;
+    property public final int captureStatus;
+    field public static final int CAPTURE_STATUS_CANCELLED = 2; // 0x2
+    field public static final int CAPTURE_STATUS_FAILED = 1; // 0x1
+    field public static final int CAPTURE_STATUS_SUCCESSFUL = 0; // 0x0
+    field public static final androidx.camera.testing.imagecapture.CaptureResult.Companion Companion;
+  }
+
+  public static final class CaptureResult.Companion {
+    method public androidx.camera.testing.imagecapture.CaptureResult cancelledResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult failedResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult successfulResult();
+    method public androidx.camera.testing.imagecapture.CaptureResult successfulResult(optional androidx.camera.testing.fakes.FakeCameraCaptureResult fakeCameraCaptureResult);
+  }
+
+}
+
diff --git a/camera/camera-testing/build.gradle b/camera/camera-testing/build.gradle
index 3b5a46e..41b7885 100644
--- a/camera/camera-testing/build.gradle
+++ b/camera/camera-testing/build.gradle
@@ -39,21 +39,24 @@
     implementation("androidx.test:rules:$testRulesVersion")
     implementation("androidx.test:runner:$testRunnerVersion")
 
-    implementation(libs.testUiautomator)
     api("androidx.annotation:annotation:1.8.1")
-    implementation(libs.guavaListenableFuture)
-    implementation("androidx.appcompat:appcompat:1.1.0")
     api(project(":camera:camera-core"))
     api(project(":camera:camera-lifecycle"))
     api(project(":camera:camera-video"))
+
+    implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.core:core:1.1.0")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
     implementation("androidx.test.espresso:espresso-core:3.5.0")
     implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
+
+    implementation(libs.atomicFu)
+    implementation(libs.guavaListenableFuture)
     implementation(libs.junit)
     implementation(libs.truth)
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesAndroid)
+    implementation(libs.testUiautomator)
 
     testImplementation("androidx.test:core:$testCoreVersion")
     testImplementation("androidx.test:runner:$testRunnerVersion")
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeAppConfig.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeAppConfig.java
index 41958c4..8135b3f 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeAppConfig.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeAppConfig.java
@@ -29,6 +29,9 @@
 import androidx.camera.testing.impl.fakes.FakeCameraFactory;
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Convenience class for generating a fake {@link CameraXConfig}.
  *
@@ -59,20 +62,10 @@
      */
     @NonNull
     public static CameraXConfig create(@Nullable CameraSelector availableCamerasSelector) {
+        FakeCameraFactory cameraFactory = createCameraFactory(availableCamerasSelector);
+
         final CameraFactory.Provider cameraFactoryProvider =
-                (ignored1, ignored2, ignored3, ignore4) -> {
-                    final FakeCameraFactory cameraFactory = new FakeCameraFactory(
-                            availableCamerasSelector);
-                    cameraFactory.insertCamera(CameraSelector.LENS_FACING_BACK,
-                            DEFAULT_BACK_CAMERA_ID,
-                            FakeAppConfig::getBackCamera);
-                    cameraFactory.insertCamera(CameraSelector.LENS_FACING_FRONT,
-                            DEFAULT_FRONT_CAMERA_ID,
-                            FakeAppConfig::getFrontCamera);
-                    final CameraCoordinator cameraCoordinator = new FakeCameraCoordinator();
-                    cameraFactory.setCameraCoordinator(cameraCoordinator);
-                    return cameraFactory;
-                };
+                (ignored1, ignored2, ignored3, ignore4) -> cameraFactory;
 
         final CameraDeviceSurfaceManager.Provider surfaceManagerProvider =
                 (ignored1, ignored2, ignored3) -> new FakeCameraDeviceSurfaceManager();
@@ -80,7 +73,14 @@
         final CameraXConfig.Builder appConfigBuilder = new CameraXConfig.Builder()
                 .setCameraFactoryProvider(cameraFactoryProvider)
                 .setDeviceSurfaceManagerProvider(surfaceManagerProvider)
-                .setUseCaseConfigFactoryProvider(ignored -> new FakeUseCaseConfigFactory());
+                .setUseCaseConfigFactoryProvider(ignored -> {
+                    List<FakeCamera> fakeCameras = new ArrayList<>();
+                    for (String cameraId : cameraFactory.getAvailableCameraIds()) {
+                        fakeCameras.add((FakeCamera) cameraFactory.getCamera(cameraId));
+                    }
+
+                    return new FakeUseCaseConfigFactory(fakeCameras);
+                });
 
         if (availableCamerasSelector != null) {
             appConfigBuilder.setAvailableCamerasLimiter(availableCamerasSelector);
@@ -89,6 +89,21 @@
         return appConfigBuilder.build();
     }
 
+    private static FakeCameraFactory createCameraFactory(
+            @Nullable CameraSelector availableCamerasSelector) {
+        FakeCameraFactory cameraFactory = new FakeCameraFactory(availableCamerasSelector);
+        cameraFactory.insertCamera(
+                CameraSelector.LENS_FACING_BACK,
+                DEFAULT_BACK_CAMERA_ID,
+                FakeAppConfig::getBackCamera);
+        cameraFactory.insertCamera(CameraSelector.LENS_FACING_FRONT,
+                DEFAULT_FRONT_CAMERA_ID,
+                FakeAppConfig::getFrontCamera);
+        final CameraCoordinator cameraCoordinator = new FakeCameraCoordinator();
+        cameraFactory.setCameraCoordinator(cameraCoordinator);
+        return cameraFactory;
+    }
+
     /**
      * Returns the default fake back camera that is used internally by CameraX.
      */
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraCaptureResult.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
similarity index 86%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraCaptureResult.java
rename to camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
index 8be8b5d5..11884ff 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraCaptureResult.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,21 +14,17 @@
  * limitations under the License.
  */
 
-package androidx.camera.testing.impl.fakes;
+package androidx.camera.testing.fakes;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
 import androidx.camera.core.impl.CameraCaptureMetaData;
 import androidx.camera.core.impl.CameraCaptureResult;
 import androidx.camera.core.impl.TagBundle;
 
 /**
  * A fake implementation of {@link CameraCaptureResult} where the values are settable.
- *
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
 public final class FakeCameraCaptureResult implements CameraCaptureResult {
     private CameraCaptureMetaData.AfMode mAfMode = CameraCaptureMetaData.AfMode.UNKNOWN;
     private CameraCaptureMetaData.AfState mAfState = CameraCaptureMetaData.AfState.UNKNOWN;
@@ -72,7 +68,7 @@
         mTimestamp = timestamp;
     }
 
-    public void setTag(@NonNull TagBundle tag) {
+    public void setTagBundle(@NonNull TagBundle tag) {
         mTag = tag;
     }
 
@@ -134,8 +130,7 @@
      *
      */
     @SuppressWarnings("unused")
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    public static class Builder {
+    public static final class Builder {
         private CameraCaptureMetaData.AfMode mAfMode = CameraCaptureMetaData.AfMode.UNKNOWN;
         private CameraCaptureMetaData.AfState mAfState = CameraCaptureMetaData.AfState.UNKNOWN;
         private CameraCaptureMetaData.AeState mAeState = CameraCaptureMetaData.AeState.UNKNOWN;
@@ -145,7 +140,7 @@
         private long mTimestamp = -1L;
         private TagBundle mTag = TagBundle.emptyBundle();
 
-        /** Construct and return a new instance of a {@link FakeCameraCaptureResult}. */
+        /** Constructs and returns a new instance of a {@link FakeCameraCaptureResult}. */
         @NonNull public FakeCameraCaptureResult build() {
             FakeCameraCaptureResult fakeCameraCaptureResult = new FakeCameraCaptureResult();
             fakeCameraCaptureResult.setAfMode(mAfMode);
@@ -154,56 +149,56 @@
             fakeCameraCaptureResult.setAwbState(mAwbState);
             fakeCameraCaptureResult.setFlashState(mFlashState);
             fakeCameraCaptureResult.setTimestamp(mTimestamp);
-            fakeCameraCaptureResult.setTag(mTag);
+            fakeCameraCaptureResult.setTagBundle(mTag);
 
             return fakeCameraCaptureResult;
         }
 
-        /** Set the {@link CameraCaptureMetaData.AfMode} */
+        /** Sets the {@link CameraCaptureMetaData.AfMode} */
         @NonNull
         public Builder setAfMode(@Nullable CameraCaptureMetaData.AfMode mode) {
             mAfMode = mode;
             return this;
         }
 
-        /** Set the {@link CameraCaptureMetaData.AfState} */
+        /** Sets the {@link CameraCaptureMetaData.AfState} */
         @NonNull
         public Builder setAfState(@Nullable CameraCaptureMetaData.AfState state) {
             mAfState = state;
             return this;
         }
 
-        /** Set the {@link CameraCaptureMetaData.AeState} */
+        /** Sets the {@link CameraCaptureMetaData.AeState} */
         @NonNull
         public Builder setAeState(@Nullable CameraCaptureMetaData.AeState state) {
             mAeState = state;
             return this;
         }
 
-        /** Set the {@link CameraCaptureMetaData.AwbState} */
+        /** Sets the {@link CameraCaptureMetaData.AwbState} */
         @NonNull
         public Builder setAwbState(@Nullable CameraCaptureMetaData.AwbState state) {
             mAwbState = state;
             return this;
         }
 
-        /** Set the {@link CameraCaptureMetaData.FlashState} */
+        /** Sets the {@link CameraCaptureMetaData.FlashState} */
         @NonNull
         public Builder setFlashState(@Nullable CameraCaptureMetaData.FlashState state) {
             mFlashState = state;
             return this;
         }
 
-        /** Set the timestamp. */
+        /** Sets the timestamp. */
         @NonNull
         public Builder setTimestamp(long timestamp) {
             mTimestamp = timestamp;
             return this;
         }
 
-        /** Set the tag. */
+        /** Sets the {@link TagBundle}. */
         @NonNull
-        public Builder setTag(@NonNull TagBundle tag) {
+        public Builder setTagBundle(@NonNull TagBundle tag) {
             mTag = tag;
             return this;
         }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index fb34d5d..656ff62 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -17,10 +17,16 @@
 package androidx.camera.testing.fakes;
 
 import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
+import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_CANCELLED;
+import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_FAILED;
+import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_SUCCESSFUL;
 import static androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.graphics.Rect;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
@@ -41,6 +47,7 @@
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.camera.testing.imagecapture.CaptureResult;
 import androidx.camera.testing.impl.FakeCameraCapturePipeline;
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -48,10 +55,13 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A fake implementation for the {@link CameraControlInternal} interface which is capable of
@@ -73,22 +83,33 @@
         }
     };
 
+    private final Object mLock = new Object();
+
     /**
      * The executor used to invoke any callback/listener which doesn't have a dedicated executor
      * for it.
      * <p> {@link CameraXExecutors#directExecutor} via default, unless some other executor is set
-     * via {@link #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)}.
+     * via {@link #FakeCameraControl(Executor, ControlUpdateCallback)}.
      */
-    @NonNull private final Executor mExecutor;
+    @NonNull
+    private final Executor mExecutor;
     private final ControlUpdateCallback mControlUpdateCallback;
     private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
     @ImageCapture.FlashMode
     private int mFlashMode = FLASH_MODE_OFF;
-    private final ArrayList<CaptureConfig> mSubmittedCaptureRequests = new ArrayList<>();
     private Pair<Executor, OnNewCaptureRequestListener> mOnNewCaptureRequestListener;
     private MutableOptionsBundle mInteropConfig = MutableOptionsBundle.create();
-    private final ArrayList<CallbackToFutureAdapter.Completer<Void>> mSubmittedCompleterList =
-            new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private final ArrayDeque<CaptureConfig> mSubmittedCaptureRequests = new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private final ArrayDeque<CallbackToFutureAdapter.Completer<Void>> mSubmittedCompleterList =
+            new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private final ArrayDeque<CaptureResult> mCaptureResults = new ArrayDeque<>();
+
+    private final List<CaptureSuccessListener> mCaptureSuccessListeners =
+            new CopyOnWriteArrayList<>();
 
     private boolean mIsZslDisabledByUseCaseConfig = false;
     private boolean mIsZslConfigAdded = false;
@@ -108,8 +129,8 @@
      * Constructs an instance of {@link FakeCameraControl} with a no-op
      * {@link ControlUpdateCallback}.
      *
-     * @see #FakeCameraControl(CameraControlInternal.ControlUpdateCallback)
-     * @see #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)
+     * @see #FakeCameraControl(ControlUpdateCallback)
+     * @see #FakeCameraControl(Executor, ControlUpdateCallback)
      */
     public FakeCameraControl() {
         this(NO_OP_CALLBACK);
@@ -121,7 +142,7 @@
      *
      * <p> Note that callbacks will be executed on the calling thread directly via
      * {@link CameraXExecutors#directExecutor}. To specify the execution thread, use
-     * {@link #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)}.
+     * {@link #FakeCameraControl(Executor, ControlUpdateCallback)}.
      *
      * @param controlUpdateCallback {@link ControlUpdateCallback} to notify events.
      */
@@ -133,7 +154,8 @@
      * Constructs an instance of {@link FakeCameraControl} with the
      * provided {@link ControlUpdateCallback}.
      *
-     * @param executor {@link Executor} used to invoke the {@code controlUpdateCallback}.
+     * @param executor              {@link Executor} used to invoke the {@code
+     *                              controlUpdateCallback}.
      * @param controlUpdateCallback {@link ControlUpdateCallback} to notify events.
      */
     public FakeCameraControl(@NonNull Executor executor,
@@ -145,44 +167,31 @@
     /**
      * Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureCancelled},
      * which is invoked in the thread denoted by {@link #mExecutor}.
+     *
+     * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead.
      */
+    @Deprecated // TODO: b/366136115 - Remove all usages
     public void notifyAllRequestsOnCaptureCancelled() {
-        for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
-            for (CameraCaptureCallback cameraCaptureCallback :
-                    captureConfig.getCameraCaptureCallbacks()) {
-                mExecutor.execute(() -> {
-                    cameraCaptureCallback.onCaptureCancelled(captureConfig.getId());
-                });
+        while (true) {
+            if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_CANCELLED, null)) {
+                break;
             }
         }
-        for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
-            completer.setException(
-                    new ImageCaptureException(ImageCapture.ERROR_CAMERA_CLOSED, "Simulate "
-                            + "capture cancelled", null));
-        }
-        mSubmittedCompleterList.clear();
-        mSubmittedCaptureRequests.clear();
     }
 
     /**
      * Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureFailed},
      * which is invoked in the thread denoted by {@link #mExecutor}.
+     *
+     * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead.
      */
+    @Deprecated // TODO: b/366136115 - Remove all usages
     public void notifyAllRequestsOnCaptureFailed() {
-        for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
-            for (CameraCaptureCallback cameraCaptureCallback :
-                    captureConfig.getCameraCaptureCallbacks()) {
-                mExecutor.execute(() -> cameraCaptureCallback.onCaptureFailed(
-                        captureConfig.getId(),
-                        new CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)));
+        while (true) {
+            if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_FAILED, null)) {
+                break;
             }
         }
-        for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
-            completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED,
-                    "Simulate capture fail", null));
-        }
-        mSubmittedCompleterList.clear();
-        mSubmittedCaptureRequests.clear();
     }
 
     /**
@@ -190,20 +199,97 @@
      * which is invoked in the thread denoted by {@link #mExecutor}.
      *
      * @param result The {@link CameraCaptureResult} which is notified to all the callbacks.
+     * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead.
      */
+    @Deprecated // TODO: b/366136115 - Remove all usages
     public void notifyAllRequestsOnCaptureCompleted(@NonNull CameraCaptureResult result) {
-        for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
-            for (CameraCaptureCallback cameraCaptureCallback :
-                    captureConfig.getCameraCaptureCallbacks()) {
-                mExecutor.execute(() -> cameraCaptureCallback.onCaptureCompleted(
-                        captureConfig.getId(), result));
+        while (true) {
+            if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_SUCCESSFUL, result)) {
+                break;
             }
         }
-        for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
-            completer.set(null);
+    }
+
+    /**
+     * Completes the first submitted but incomplete capture request using one of the
+     * {@link CameraCaptureCallback} methods, which is invoked in the thread denoted by
+     * {@link #mExecutor}.
+     *
+     * @param captureStatus Represents how a capture request should be completed.
+     * @param captureResult The {@link CameraCaptureResult} which is notified to all the
+     *                      callbacks. Must not be null if captureStatus parameter is
+     *                      {@link CaptureResult#CAPTURE_STATUS_SUCCESSFUL}.
+     * @return True if a capture request was completed, false otherwise.
+     */
+    // TODO: b/365519650 - Take FakeCameraCaptureResult as parameter to contain extra user-provided
+    //  data like bitmap/image proxy and use that to complete capture.
+    @SuppressWarnings("ObjectToString") // Required for captureConfig hashcode log
+    private boolean completeFirstPendingCaptureRequest(
+            @CaptureResult.CaptureStatus int captureStatus,
+            @Nullable CameraCaptureResult captureResult) {
+        CaptureConfig captureConfig;
+        CallbackToFutureAdapter.Completer<Void> completer;
+
+        synchronized (mLock) {
+            if (mSubmittedCaptureRequests.isEmpty() || mSubmittedCompleterList.isEmpty()) {
+                Logger.d(TAG,
+                        "completeFirstPendingCaptureRequest: returning early since either "
+                                + "mSubmittedCaptureRequests or mSubmittedCompleterList is empty, "
+                                + "mSubmittedCaptureRequests = "
+                                + mSubmittedCaptureRequests + ", mSubmittedCompleterList"
+                                + mSubmittedCompleterList);
+                return false;
+            }
+
+            captureConfig = mSubmittedCaptureRequests.removeFirst();
+            completer = mSubmittedCompleterList.removeFirst();
         }
-        mSubmittedCompleterList.clear();
-        mSubmittedCaptureRequests.clear();
+        Logger.d(TAG, "completeFirstPendingCaptureRequest: captureConfig = " + captureConfig);
+
+        if (captureStatus == CAPTURE_STATUS_SUCCESSFUL) {
+            notifyCaptureSuccess(requireNonNull(captureResult));
+        }
+
+        for (CameraCaptureCallback cameraCaptureCallback :
+                captureConfig.getCameraCaptureCallbacks()) {
+            mExecutor.execute(() -> {
+                switch (captureStatus) {
+                    case CAPTURE_STATUS_SUCCESSFUL:
+                        cameraCaptureCallback.onCaptureCompleted(captureConfig.getId(),
+                                Objects.requireNonNull(captureResult));
+                        break;
+                    case CAPTURE_STATUS_FAILED:
+                        cameraCaptureCallback.onCaptureFailed(captureConfig.getId(),
+                                new CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR));
+                        break;
+                    case CAPTURE_STATUS_CANCELLED:
+                        cameraCaptureCallback.onCaptureCancelled(captureConfig.getId());
+                        break;
+                    default:
+                        Logger.e(TAG, "completeFirstPendingCaptureRequest: unknown capture status: "
+                                + captureStatus);
+                }
+            });
+        }
+
+        switch (captureStatus) {
+            case CAPTURE_STATUS_SUCCESSFUL:
+                completer.set(null);
+                break;
+            case CAPTURE_STATUS_FAILED:
+                completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED,
+                        "Simulate capture fail", null));
+                break;
+            case CAPTURE_STATUS_CANCELLED:
+                completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAMERA_CLOSED,
+                        "Simulate capture cancelled", null));
+                break;
+            default:
+                Logger.e(TAG, "completeFirstPendingCaptureRequest: unknown capture status: "
+                        + captureStatus);
+        }
+
+        return true;
     }
 
     @ImageCapture.FlashMode
@@ -294,24 +380,40 @@
     public ListenableFuture<List<Void>> submitStillCaptureRequests(
             @NonNull List<CaptureConfig> captureConfigs,
             int captureMode, int flashType) {
-        mSubmittedCaptureRequests.addAll(captureConfigs);
-        mExecutor.execute(
-                () -> mControlUpdateCallback.onCameraControlCaptureRequests(captureConfigs));
+        Logger.d(TAG, "submitStillCaptureRequests: captureConfigs = " + captureConfigs);
+
         List<ListenableFuture<Void>> fakeFutures = new ArrayList<>();
-        for (int i = 0; i < captureConfigs.size(); i++) {
-            fakeFutures.add(CallbackToFutureAdapter.getFuture(completer -> {
-                mSubmittedCompleterList.add(completer);
-                return "fakeFuture";
-            }));
+
+        synchronized (mLock) {
+            mSubmittedCaptureRequests.addAll(captureConfigs);
+            for (int i = 0; i < captureConfigs.size(); i++) {
+                AtomicReference<CallbackToFutureAdapter.Completer<Void>> completerRef =
+                        new AtomicReference<>();
+                fakeFutures.add(CallbackToFutureAdapter.getFuture(completer -> {
+                    // mSubmittedCaptureRequests and mSubmittedCompleterList must be updated under
+                    // the same lock to avoid rare out-of-state bugs. So, completer can't be added
+                    // to mSubmittedCompleterList here directly even though this line is guaranteed
+                    // to be called immediately.
+                    completerRef.set(completer);
+                    return "fakeFuture";
+                }));
+                mSubmittedCompleterList.add(Objects.requireNonNull(completerRef.get()));
+            }
         }
 
+        mExecutor.execute(
+                () -> mControlUpdateCallback.onCameraControlCaptureRequests(captureConfigs));
+
         if (mOnNewCaptureRequestListener != null) {
-            Executor executor = Objects.requireNonNull(mOnNewCaptureRequestListener.first);
+            Executor executor = requireNonNull(mOnNewCaptureRequestListener.first);
             OnNewCaptureRequestListener listener =
-                    Objects.requireNonNull(mOnNewCaptureRequestListener.second);
+                    requireNonNull(mOnNewCaptureRequestListener.second);
 
             executor.execute(() -> listener.onNewCaptureRequests(captureConfigs));
         }
+
+        mExecutor.execute(this::applyCaptureResults);
+
         return Futures.allAsList(fakeFutures);
     }
 
@@ -369,7 +471,7 @@
      * {@link #setOnNewCaptureRequestListener(Executor, OnNewCaptureRequestListener)}.
      *
      * @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted
-     * {@link CaptureConfig} parameters when new capture requests are submitted.
+     *                 {@link CaptureConfig} parameters when new capture requests are submitted.
      */
     public void setOnNewCaptureRequestListener(@NonNull OnNewCaptureRequestListener listener) {
         setOnNewCaptureRequestListener(CameraXExecutors.directExecutor(), listener);
@@ -380,7 +482,7 @@
      *
      * @param executor {@link Executor} used to notify the {@code listener}.
      * @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted
-     * {@link CaptureConfig} parameters when new capture requests are submitted.
+     *                 {@link CaptureConfig} parameters when new capture requests are submitted.
      */
     public void setOnNewCaptureRequestListener(@NonNull Executor executor,
             @NonNull OnNewCaptureRequestListener listener) {
@@ -444,9 +546,108 @@
         return MutableOptionsBundle.from(mInteropConfig);
     }
 
+    /**
+     * Submits a {@link CaptureResult} to be used for the first pending capture request.
+     *
+     * <p> If there are no pending capture requests, the `CaptureResult` is kept in a queue to be
+     * used in future capture requests.
+     *
+     * <p> This method will complete a corresponding capture request according to the provided
+     * capture result.
+     *
+     * <p> For applying a capture result to all already submitted capture requests, use the
+     * {@link #completeAllCaptureRequests} method instead.
+     */
+    public void submitCaptureResult(@NonNull CaptureResult captureResult) {
+        synchronized (mLock) {
+            mCaptureResults.add(captureResult);
+        }
+        applyCaptureResults();
+    }
+
+    /**
+     * Completes all the incomplete capture requests with the provided {@link CaptureResult}.
+     *
+     * <p> Note that {@link ImageCapture#takePicture} methods send requests to camera asynchronously
+     * and thus a capture request from {@link ImageCapture} may not be available immediately.
+     * Consider using {@link #setOnNewCaptureRequestListener} to know when a capture request has
+     * been submitted before using this method right after {@code ImageCapture#takePicture}.
+     * Furthermore, {@code ImageCapture} queues capture requests before submitting to camera when
+     * multiple captures are requested. So it is recommended to use {@link #submitCaptureResult}
+     * whenever possible to avoid confusing and complicated scenario in integration tests.
+     */
+    public void completeAllCaptureRequests(@NonNull CaptureResult captureResult) {
+        synchronized (mLock) {
+            // Add CaptureResult instances for all pending requests first.
+            for (int i = 0; i < mSubmittedCaptureRequests.size(); i++) {
+                mCaptureResults.add(captureResult);
+            }
+        }
+
+        applyCaptureResults();
+    }
+
+    private void applyCaptureResults() {
+        synchronized (mLock) {
+            while (!mCaptureResults.isEmpty()) {
+                CaptureResult captureResult = mCaptureResults.getFirst();
+
+                if (completeFirstPendingCaptureRequest(captureResult.getCaptureStatus(),
+                        captureResult.getCameraCaptureResult())) {
+                    mCaptureResults.removeFirst();
+                } else {
+                    Logger.d(TAG, "applyCaptureResults: failed to notify");
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a listener to be notified when there are completed capture requests.
+     *
+     * @param listener {@link CaptureSuccessListener} that is notified with the submitted
+     *                 {@link CaptureConfig} parameters when capture requests are completed.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void addCaptureSuccessListener(@NonNull CaptureSuccessListener listener) {
+        mCaptureSuccessListeners.add(listener);
+    }
+
+    /**
+     * Removes a {@link CaptureSuccessListener} if it exist.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void removeCaptureSuccessListener(@NonNull CaptureSuccessListener listener) {
+        mCaptureSuccessListeners.remove(listener);
+    }
+
+    private void notifyCaptureSuccess(@NonNull CameraCaptureResult result) {
+        Logger.d(TAG, "notifyCaptureComplete: mCaptureCompleteListeners = "
+                + mCaptureSuccessListeners);
+        for (CaptureSuccessListener listener : mCaptureSuccessListeners) {
+            listener.onCompleted(result);
+        }
+    }
+
     /** A listener which is used to notify when there are new submitted capture requests */
     public interface OnNewCaptureRequestListener {
         /** Called when there are new submitted capture request */
         void onNewCaptureRequests(@NonNull List<CaptureConfig> captureConfigs);
     }
+
+    /**
+     * A listener which is used to notify when submitted capture requests are completed
+     * successfully.
+     *
+     * <p> The reason we need to listen to success case specifically is because of how CameraX image
+     * capture flow works internally. In case of success, a real android.media.Image instance is
+     * also expected from ImageReader which makes this kind of listener necessary for the proper
+     * implementation of a fake TakePictureManager.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public interface CaptureSuccessListener {
+        /** Called when a submitted capture request has been completed successfully. */
+        void onCompleted(@NonNull CameraCaptureResult result);
+    }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/imagecapture/CaptureResult.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/imagecapture/CaptureResult.kt
new file mode 100644
index 0000000..8184d01d
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/imagecapture/CaptureResult.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.testing.imagecapture
+
+import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.CAPTURE_STATUS_CANCELLED
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.CAPTURE_STATUS_FAILED
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.CAPTURE_STATUS_SUCCESSFUL
+
+/**
+ * The capture result info used for a fake image capture completion.
+ *
+ * If [captureStatus] is [CAPTURE_STATUS_SUCCESSFUL], [cameraCaptureResult] is guaranteed to be
+ * non-null (`FakeCameraCaptureResult()` is used by default if user didn't provide any).
+ *
+ * If [captureStatus] is [CAPTURE_STATUS_FAILED] or [CAPTURE_STATUS_CANCELLED],
+ * [cameraCaptureResult] is usually null.
+ *
+ * @see FakeCameraControl.submitCaptureResult
+ */
+public class CaptureResult
+private constructor(
+    public val captureStatus: @CaptureStatus Int,
+    public val cameraCaptureResult: FakeCameraCaptureResult? = null
+) {
+    /**
+     * The capture result status used in fake image capture completion.
+     *
+     * @see CaptureResult
+     */
+    @Target(AnnotationTarget.TYPE)
+    @IntDef(CAPTURE_STATUS_SUCCESSFUL, CAPTURE_STATUS_FAILED, CAPTURE_STATUS_CANCELLED)
+    @Retention(AnnotationRetention.SOURCE)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public annotation class CaptureStatus
+
+    public companion object {
+        /** Represents a successful [CaptureStatus]. */
+        public const val CAPTURE_STATUS_SUCCESSFUL: Int = 0
+
+        /** Represents a failed [CaptureStatus]. */
+        public const val CAPTURE_STATUS_FAILED: Int = 1
+
+        /** Represents a canceled [CaptureStatus]. */
+        public const val CAPTURE_STATUS_CANCELLED: Int = 2
+
+        /** Represents a successful [CaptureResult]. */
+        @JvmStatic
+        @JvmOverloads
+        public fun successfulResult(
+            fakeCameraCaptureResult: FakeCameraCaptureResult = FakeCameraCaptureResult()
+        ): CaptureResult =
+            CaptureResult(
+                captureStatus = CAPTURE_STATUS_SUCCESSFUL,
+                cameraCaptureResult = fakeCameraCaptureResult
+            )
+
+        /** Represents a failed [CaptureResult]. */
+        @JvmStatic
+        public fun failedResult(): CaptureResult =
+            CaptureResult(captureStatus = CAPTURE_STATUS_FAILED)
+
+        /** Represents a cancelled [CaptureResult]. */
+        @JvmStatic
+        public fun cancelledResult(): CaptureResult =
+            CaptureResult(
+                captureStatus = CAPTURE_STATUS_CANCELLED,
+            )
+    }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt
index e960662..015bff9 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt
@@ -16,8 +16,11 @@
 
 package androidx.camera.testing.impl
 
+import android.graphics.Bitmap
 import android.graphics.Rect
+import android.util.Size
 import android.view.Surface
+import androidx.camera.core.Logger
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.futures.FutureCallback
@@ -36,7 +39,7 @@
 private const val TAG = "CaptureSimulation"
 
 /** Simulates a capture frame being drawn on all of the provided surfaces. */
-public suspend fun List<DeferrableSurface>.simulateCaptureFrame(): Unit = forEach {
+internal suspend fun List<DeferrableSurface>.simulateCaptureFrame(): Unit = forEach {
     it.simulateCaptureFrame()
 }
 
@@ -45,7 +48,7 @@
  *
  * @throws IllegalStateException If [DeferrableSurface.getSurface] provides a null surface.
  */
-public suspend fun DeferrableSurface.simulateCaptureFrame() {
+internal suspend fun DeferrableSurface.simulateCaptureFrame() {
     val deferred = CompletableDeferred<Unit>()
 
     Futures.addCallback(
@@ -53,6 +56,7 @@
         object : FutureCallback<Surface?> {
             override fun onSuccess(surface: Surface?) {
                 if (surface == null) {
+                    Logger.w(TAG, "simulateCaptureFrame: surface obtained from $this is null!")
                     deferred.completeExceptionally(
                         IllegalStateException(
                             "Null surface obtained from ${this@simulateCaptureFrame}"
@@ -60,10 +64,9 @@
                     )
                     return
                 }
-                val canvas =
-                    surface.lockCanvas(Rect(0, 0, prescribedSize.width, prescribedSize.height))
                 // TODO: Draw something on the canvas (e.g. fake image bitmap or alternating color).
-                surface.unlockCanvasAndPost(canvas)
+                surface.simulateCaptureFrame(prescribedSize)
+
                 deferred.complete(Unit)
             }
 
@@ -77,6 +80,20 @@
     deferred.await()
 }
 
+/**
+ * Simulates a capture frame being drawn on a [Surface].
+ *
+ * @param canvasSize The canvas size for drawing.
+ * @param bitmap A bitmap to draw as the capture frame, if not null.
+ */
+internal fun Surface.simulateCaptureFrame(canvasSize: Size, bitmap: Bitmap? = null) {
+    val canvas = lockCanvas(Rect(0, 0, canvasSize.width, canvasSize.height))
+    if (bitmap != null) {
+        canvas.drawBitmap(bitmap, null, Rect(0, 0, canvasSize.width, canvasSize.height), null)
+    }
+    unlockCanvasAndPost(canvas)
+}
+
 // The following methods are adapters for Java invocations.
 
 /**
@@ -88,7 +105,7 @@
  * @return A [ListenableFuture] representing when the operation has been completed.
  */
 @JvmOverloads
-public fun List<DeferrableSurface>.simulateCaptureFrameAsync(
+internal fun List<DeferrableSurface>.simulateCaptureFrameAsync(
     executor: Executor = Dispatchers.Default.asExecutor()
 ): ListenableFuture<Void> {
     val scope = CoroutineScope(SupervisorJob() + executor.asCoroutineDispatcher())
@@ -104,7 +121,7 @@
  * @return A [ListenableFuture] representing when the operation has been completed.
  */
 @JvmOverloads
-public fun DeferrableSurface.simulateCaptureFrameAsync(
+internal fun DeferrableSurface.simulateCaptureFrameAsync(
     executor: Executor = Dispatchers.Default.asExecutor()
 ): ListenableFuture<Void> {
     val scope = CoroutineScope(SupervisorJob() + executor.asCoroutineDispatcher())
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/SurfaceTextureProvider.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/SurfaceTextureProvider.java
index cd21770..8366412 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/SurfaceTextureProvider.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/SurfaceTextureProvider.java
@@ -41,6 +41,7 @@
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This class creates implementations of PreviewSurfaceProvider that provide Surfaces that have been
@@ -203,6 +204,8 @@
             @Nullable Runnable onClosed) {
         return CallbackToFutureAdapter.getFuture((completer) -> {
             glExecutor.execute(() -> {
+                Object lock = new Object();
+                AtomicBoolean surfaceTextureReleased = new AtomicBoolean(false);
                 EGLContextParams contextParams = createDummyEGLContext();
                 EGL14.eglMakeCurrent(contextParams.display, contextParams.outputSurface,
                         contextParams.outputSurface, contextParams.context);
@@ -212,14 +215,22 @@
                 surfaceTexture.setDefaultBufferSize(width, height);
                 surfaceTexture.setOnFrameAvailableListener(it ->
                         glExecutor.execute(() -> {
-                            it.updateTexImage();
-                            if (frameAvailableListener != null) {
-                                frameAvailableListener.onFrameAvailable(surfaceTexture);
+                            synchronized (lock) {
+                                if (surfaceTextureReleased.get()) {
+                                    return;
+                                }
+                                it.updateTexImage();
+                                if (frameAvailableListener != null) {
+                                    frameAvailableListener.onFrameAvailable(surfaceTexture);
+                                }
                             }
                         }));
 
                 completer.set(
                         new SurfaceTextureHolder(surfaceTexture, () -> glExecutor.execute(() -> {
+                            synchronized (lock) {
+                                surfaceTextureReleased.set(true);
+                            }
                             surfaceTexture.release();
                             GLES20.glDeleteTextures(1, textureIds, 0);
                             terminateEGLContext(contextParams);
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
index 1914e96..c5ac789 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
@@ -40,7 +40,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.ImageInfo;
 import androidx.camera.core.internal.CameraCaptureResultImageInfo;
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
+import androidx.camera.testing.fakes.FakeCameraCaptureResult;
 import androidx.camera.testing.impl.fakes.FakeImageProxy;
 import androidx.camera.testing.impl.fakes.FakeJpegPlaneProxy;
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageProxy.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageProxy.java
index 90c4bce..5acfa2bb 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageProxy.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageProxy.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.testing.impl.fakes;
 
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.media.Image;
 
@@ -47,6 +48,8 @@
     @NonNull
     private ImageInfo mImageInfo;
     private Image mImage;
+    @Nullable
+    private Bitmap mBitmap;
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     final Object mReleaseLock = new Object();
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -60,6 +63,11 @@
         mImageInfo = imageInfo;
     }
 
+    public FakeImageProxy(@NonNull ImageInfo imageInfo, @NonNull Bitmap bitmap) {
+        mImageInfo = imageInfo;
+        mBitmap = bitmap;
+    }
+
     @Override
     public void close() {
         synchronized (mReleaseLock) {
@@ -196,4 +204,13 @@
             return mReleaseFuture;
         }
     }
+
+    @NonNull
+    @Override
+    public Bitmap toBitmap() {
+        if (mBitmap != null) {
+            return mBitmap;
+        }
+        return ImageProxy.super.toBitmap();
+    }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
index 135f64c..f5157da 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
@@ -22,6 +22,7 @@
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
+import androidx.camera.core.Logger
 import androidx.camera.core.impl.utils.Exif
 import com.google.common.truth.Truth
 import java.io.ByteArrayInputStream
@@ -66,6 +67,7 @@
     public val errors: MutableList<ImageCaptureException> = mutableListOf()
 
     override fun onCaptureSuccess(image: ImageProxy) {
+        Logger.d(TAG, "onCaptureSuccess: image = $image")
         results.add(
             CapturedImage(
                 image = image,
@@ -86,6 +88,7 @@
     }
 
     override fun onError(exception: ImageCaptureException) {
+        Logger.d(TAG, "onError", exception)
         errors.add(exception)
         latch.countDown()
     }
@@ -105,6 +108,11 @@
         Truth.assertThat(withTimeoutOrNull(timeout) { latch.await() }).isNotNull()
     }
 
+    /** Asserts that capture hasn't been completed within the provided `duration`. */
+    public suspend fun assertNoCapture(timeout: Duration = CAPTURE_TIMEOUT) {
+        Truth.assertThat(withTimeoutOrNull(timeout) { latch.await() }).isNull()
+    }
+
     public suspend fun awaitCapturesAndAssert(
         timeout: Duration = CAPTURE_TIMEOUT,
         capturedImagesCount: Int = 0,
@@ -137,6 +145,7 @@
     }
 
     public companion object {
+        private const val TAG = "FakeOnImageCaptureCallback"
         private val CAPTURE_TIMEOUT = 15.seconds
     }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeTakePictureManagerImpl.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeTakePictureManagerImpl.kt
deleted file mode 100644
index e1b2f99..0000000
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeTakePictureManagerImpl.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.testing.impl.fakes
-
-import androidx.annotation.VisibleForTesting
-import androidx.camera.core.imagecapture.ImagePipeline
-import androidx.camera.core.imagecapture.RequestWithCallback
-import androidx.camera.core.imagecapture.TakePictureManager
-import androidx.camera.core.imagecapture.TakePictureRequest
-
-private const val TAG = "FakeTakePictureManager"
-
-internal class FakeTakePictureManagerImpl : TakePictureManager {
-    override fun setImagePipeline(imagePipeline: ImagePipeline) {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    override fun offerRequest(takePictureRequest: TakePictureRequest) {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    override fun pause() {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    override fun resume() {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    override fun abortRequests() {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    @VisibleForTesting
-    override fun hasCapturingRequest(): Boolean {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    @VisibleForTesting
-    override fun getCapturingRequest(): RequestWithCallback? {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    @VisibleForTesting
-    override fun getIncompleteRequests(): List<RequestWithCallback> {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-
-    @VisibleForTesting
-    override fun getImagePipeline(): ImagePipeline {
-        throw UnsupportedOperationException("Not implemented yet")
-    }
-}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeUseCaseConfigFactory.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeUseCaseConfigFactory.java
index f364c4e..54dac3a 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeUseCaseConfigFactory.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeUseCaseConfigFactory.java
@@ -19,6 +19,7 @@
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_CONFIG_UNPACKER;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_DEFAULT_SESSION_CONFIG;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_TAKE_PICTURE_MANAGER_PROVIDER;
 
 import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraDevice;
@@ -30,21 +31,44 @@
 import androidx.camera.core.ExperimentalZeroShutterLag;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCapture.CaptureMode;
+import androidx.camera.core.imagecapture.ImageCaptureControl;
+import androidx.camera.core.imagecapture.TakePictureManager;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.OptionsBundle;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.testing.fakes.FakeCamera;
+import androidx.camera.testing.impl.wrappers.TakePictureManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A fake implementation of {@link UseCaseConfigFactory}.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public final class FakeUseCaseConfigFactory implements UseCaseConfigFactory {
-
     @Nullable
     private CaptureType mLastRequestedCaptureType;
 
+    @NonNull
+    private final List<FakeCamera> mFakeCameras = new ArrayList<>();
+
+    /**
+     * Creates a {@link FakeUseCaseConfigFactory} instance.
+     */
+    public FakeUseCaseConfigFactory() {
+    }
+
+    /**
+     * Creates a {@link FakeUseCaseConfigFactory} instance with the available {@link FakeCamera}
+     * instances.
+     */
+    public FakeUseCaseConfigFactory(@NonNull List<FakeCamera> fakeCameras) {
+        mFakeCameras.addAll(fakeCameras);
+    }
+
     /**
      * Returns the configuration for the given capture type, or <code>null</code> if the
      * configuration cannot be produced.
@@ -66,6 +90,18 @@
         mutableConfig.insertOption(OPTION_SESSION_CONFIG_UNPACKER,
                 new FakeSessionConfigOptionUnpacker());
 
+        if (captureType == CaptureType.IMAGE_CAPTURE) {
+            mutableConfig.insertOption(OPTION_TAKE_PICTURE_MANAGER_PROVIDER,
+                    new TakePictureManager.Provider() {
+                        @NonNull
+                        @Override
+                        public TakePictureManager newInstance(
+                                @NonNull ImageCaptureControl imageCaptureControl) {
+                            return new TakePictureManagerWrapper(imageCaptureControl, mFakeCameras);
+                        }
+                    });
+        }
+
         return OptionsBundle.from(mutableConfig);
     }
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/wrappers/TakePictureManagerWrapper.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/wrappers/TakePictureManagerWrapper.kt
new file mode 100644
index 0000000..a035008
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/wrappers/TakePictureManagerWrapper.kt
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.testing.impl.wrappers
+
+import android.graphics.Bitmap
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCapture.OutputFileOptions
+import androidx.camera.core.ImageCapture.OutputFileResults
+import androidx.camera.core.ImageProcessingUtil
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.Logger
+import androidx.camera.core.imagecapture.Bitmap2JpegBytes
+import androidx.camera.core.imagecapture.ImageCaptureControl
+import androidx.camera.core.imagecapture.ImagePipeline
+import androidx.camera.core.imagecapture.JpegBytes2Disk
+import androidx.camera.core.imagecapture.JpegBytes2Image
+import androidx.camera.core.imagecapture.RequestWithCallback
+import androidx.camera.core.imagecapture.TakePictureManager
+import androidx.camera.core.imagecapture.TakePictureManagerImpl
+import androidx.camera.core.imagecapture.TakePictureRequest
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.processing.Packet
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.fakes.FakeCameraControl.CaptureSuccessListener
+import androidx.camera.testing.impl.ExifUtil
+import androidx.camera.testing.impl.TestImageUtil
+import androidx.camera.testing.impl.TestImageUtil.createBitmap
+import androidx.camera.testing.impl.fakes.FakeImageInfo
+import androidx.camera.testing.impl.fakes.FakeImageProxy
+import kotlinx.atomicfu.atomic
+
+/**
+ * A [TakePictureManager] implementation wrapped around the real implementation
+ * [TakePictureManagerImpl].
+ *
+ * It is used for fake cameras and provides fake image capture results when required from a camera.
+ */
+public class TakePictureManagerWrapper(
+    imageCaptureControl: ImageCaptureControl,
+    private val fakeCameras: List<FakeCamera>
+) : TakePictureManager {
+    // Try to keep the fake as close to real as possible
+    private val managerDelegate = TakePictureManagerImpl(imageCaptureControl)
+
+    private val bitmap2JpegBytes = Bitmap2JpegBytes()
+    private val jpegBytes2Disk = JpegBytes2Disk()
+    private val jpegBytes2Image = JpegBytes2Image()
+
+    private val imageProxyQueue = ArrayDeque<ImageProxy>()
+    private val outputFileResultsQueue = ArrayDeque<OutputFileResults>()
+
+    private val pendingRequestCount = atomic(0)
+
+    private val captureCompleteListener = CaptureSuccessListener { _ ->
+        completeCapturingRequest()
+
+        // TODO - handle CameraControl receiving more than one capture per takePictureRequest
+        if (pendingRequestCount.decrementAndGet() == 0) {
+            removeCaptureCompleteListener()
+        }
+    }
+
+    private fun addCaptureCompleteListener() {
+        Logger.d(TAG, "addCaptureCompleteListener: fakeCameras = $fakeCameras")
+
+        fakeCameras.forEach { camera ->
+            if (camera.cameraControlInternal is FakeCameraControl) {
+                (camera.cameraControlInternal as FakeCameraControl).addCaptureSuccessListener(
+                    captureCompleteListener
+                )
+            } else {
+                Logger.w(
+                    TAG,
+                    "Ignoring ${camera.cameraControlInternal} as it's not FakeCameraControl!"
+                )
+            }
+        }
+    }
+
+    private fun removeCaptureCompleteListener() {
+        Logger.d(TAG, "removeCaptureCompleteListener: fakeCameras = $fakeCameras")
+
+        fakeCameras.forEach { camera ->
+            if (camera.cameraControlInternal is FakeCameraControl) {
+                (camera.cameraControlInternal as FakeCameraControl).removeCaptureSuccessListener(
+                    captureCompleteListener
+                )
+            } else {
+                Logger.w(
+                    TAG,
+                    "Ignoring ${camera.cameraControlInternal} as it's not FakeCameraControl!"
+                )
+            }
+        }
+    }
+
+    override fun setImagePipeline(imagePipeline: ImagePipeline) {
+        managerDelegate.imagePipeline = imagePipeline
+    }
+
+    override fun offerRequest(takePictureRequest: TakePictureRequest) {
+        if (pendingRequestCount.getAndIncrement() == 0) {
+            addCaptureCompleteListener()
+        }
+
+        managerDelegate.offerRequest(takePictureRequest)
+    }
+
+    override fun pause() {
+        managerDelegate.pause()
+    }
+
+    override fun resume() {
+        managerDelegate.resume()
+    }
+
+    override fun abortRequests() {
+        managerDelegate.abortRequests()
+    }
+
+    @VisibleForTesting
+    override fun hasCapturingRequest(): Boolean = managerDelegate.hasCapturingRequest()
+
+    @VisibleForTesting
+    override fun getCapturingRequest(): RequestWithCallback? = managerDelegate.capturingRequest
+
+    @VisibleForTesting
+    override fun getIncompleteRequests(): List<RequestWithCallback> =
+        managerDelegate.incompleteRequests
+
+    @VisibleForTesting
+    override fun getImagePipeline(): ImagePipeline = managerDelegate.imagePipeline
+
+    @VisibleForTesting
+    private fun completeCapturingRequest() {
+        Log.d(
+            TAG,
+            "completeCapturingRequest: capturingRequest = ${managerDelegate.capturingRequest}"
+        )
+        managerDelegate.capturingRequest?.apply {
+            runOnMainThread { // onCaptureStarted, onImageCaptured etc. are @MainThread annotated
+                Logger.d(TAG, "completeCapturingRequest: runOnMainThread")
+                onCaptureStarted()
+                onImageCaptured()
+
+                // TODO: b/365519650 - Take FakeCameraCaptureResult as parameter to contain extra
+                //  user-provided data like bitmap/image proxy and use that to complete capture.
+                val bitmap = takePictureRequest.createBitmap()
+
+                val outputFileOptions =
+                    takePictureRequest.outputFileOptions // enables smartcast for null check
+
+                if (takePictureRequest.onDiskCallback != null && outputFileOptions != null) {
+                    if (outputFileResultsQueue.isEmpty()) {
+                        if (outputFileOptions.size > 1) {
+                            Logger.w(
+                                TAG,
+                                "Simultaneous capture not supported, outputFileOptions = $outputFileOptions"
+                            )
+                        }
+                        onFinalResult(
+                            createOutputFileResults(
+                                takePictureRequest,
+                                outputFileOptions[0],
+                                bitmap
+                            )
+                        )
+                    } else {
+                        onFinalResult(outputFileResultsQueue.removeFirst())
+                    }
+                } else {
+                    if (imageProxyQueue.isEmpty()) {
+                        onFinalResult(createImageProxy(takePictureRequest, bitmap))
+                    } else {
+                        onFinalResult(imageProxyQueue.removeFirst())
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Enqueues an [ImageProxy] to be used as result for the next image capture with
+     * [ImageCapture.OnImageCapturedCallback].
+     *
+     * Note that the provided [ImageProxy] is consumed by next image capture and is not available
+     * for following captures. If no result is available during a capture, CameraX will create a
+     * fake image by itself and provide result based on that.
+     */
+    public fun enqueueImageProxy(imageProxy: ImageProxy) {
+        imageProxyQueue.add(imageProxy)
+    }
+
+    /**
+     * Enqueues an [OutputFileResults] to be used as result for the next image capture with
+     * [ImageCapture.OnImageSavedCallback].
+     *
+     * Note that the provided [OutputFileResults] is consumed by next image capture and is not
+     * available for following captures. If no result is available during a capture, CameraX will
+     * create a fake image by itself and provide result based on that.
+     */
+    public fun enqueueOutputFileResults(outputFileResults: OutputFileResults) {
+        outputFileResultsQueue.add(outputFileResults)
+    }
+
+    private fun createOutputFileResults(
+        takePictureRequest: TakePictureRequest,
+        outputFileOptions: OutputFileOptions,
+        bitmap: Bitmap,
+    ): OutputFileResults {
+        // TODO - Take a bitmap as input and use that directly
+        val bytesPacket = takePictureRequest.convertBitmapToBytes(bitmap)
+        return jpegBytes2Disk.apply(JpegBytes2Disk.In.of(bytesPacket, outputFileOptions))
+    }
+
+    private fun createImageProxy(
+        takePictureRequest: TakePictureRequest,
+        bitmap: Bitmap,
+    ): ImageProxy {
+        if (canLoadImageProcessingUtilJniLib()) {
+            try {
+                val bytesPacket = takePictureRequest.convertBitmapToBytes(bitmap)
+                return jpegBytes2Image.apply(bytesPacket).data
+            } catch (e: Exception) {
+                // We have observed this kind of issue in Pixel 2 API 26 emulator, however this is
+                // added as a general workaround as this may happen in any emulator/device and
+                // similar to how not all resolutions are supported due to device capabilities even
+                // in production code.
+                Logger.e(
+                    TAG,
+                    "createImageProxy: failed for cropRect = ${takePictureRequest.cropRect}" +
+                        " which may happen due to a high resolution not being supported" +
+                        ", trying again with 640x480",
+                    e
+                )
+
+                val bytesPacket =
+                    takePictureRequest.convertBitmapToBytes(
+                        createBitmap(640, 480),
+                        Rect(0, 0, 640, 480)
+                    )
+                Logger.d(TAG, "createImageProxy: bytesPacket size = ${bytesPacket.size}")
+
+                return jpegBytes2Image.apply(bytesPacket).data
+            }
+        } else {
+            return bitmap.toFakeImageProxy()
+        }
+    }
+
+    private fun Bitmap.toFakeImageProxy(): ImageProxy {
+        return FakeImageProxy(FakeImageInfo(), this)
+    }
+
+    private fun TakePictureRequest.createBitmap() =
+        TestImageUtil.createBitmap(cropRect.width(), cropRect.height())
+
+    private fun TakePictureRequest.convertBitmapToBytes(
+        bitmap: Bitmap,
+        cropRect: Rect = this.cropRect
+    ): Packet<ByteArray> {
+        val inputPacket =
+            Packet.of(
+                bitmap,
+                ExifUtil.createExif(
+                    TestImageUtil.createJpegBytes(cropRect.width(), cropRect.height())
+                ),
+                cropRect,
+                rotationDegrees,
+                Matrix(),
+                FakeCameraCaptureResult()
+            )
+
+        return bitmap2JpegBytes.apply(Bitmap2JpegBytes.In.of(inputPacket, jpegQuality))
+    }
+
+    private fun canLoadImageProcessingUtilJniLib(): Boolean {
+        try {
+            System.loadLibrary(ImageProcessingUtil.JNI_LIB_NAME)
+            return true
+        } catch (e: UnsatisfiedLinkError) {
+            Logger.d(TAG, "canLoadImageProcessingUtilJniLib", e)
+            return false
+        }
+    }
+
+    private fun runOnMainThread(block: () -> Any) {
+        CameraXExecutors.mainThreadExecutor().submit(block)
+    }
+
+    private companion object {
+        private const val TAG = "TakePictureManagerWrap"
+    }
+}
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraControlTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraControlTest.java
index a61ada7..0d91091 100644
--- a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraControlTest.java
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraControlTest.java
@@ -37,7 +37,7 @@
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
+import androidx.camera.testing.imagecapture.CaptureResult;
 import androidx.camera.testing.impl.mocks.MockScreenFlash;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -71,6 +71,8 @@
     }
 
     @Test
+    // TODO: b/366136115 - Remove test for deprecated API when the API is fully removed.
+    @SuppressWarnings("deprecation")
     public void notifiesAllRequestOnCaptureCancelled() {
         CountDownLatch latch = new CountDownLatch(3);
         CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
@@ -99,6 +101,36 @@
     }
 
     @Test
+    public void completeAllCaptureRequests_notifiesCaptureCancelledToAllRequests() {
+        CountDownLatch latch = new CountDownLatch(3);
+        CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCancelled(int captureConfigId) {
+                latch.countDown();
+            }
+        }, new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCancelled(int captureConfigId) {
+                latch.countDown();
+            }
+        });
+        CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCancelled(int captureConfigId) {
+                latch.countDown();
+            }
+        });
+
+        mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
+        mCameraControl.completeAllCaptureRequests(CaptureResult.cancelledResult());
+
+        awaitLatch(latch);
+    }
+
+    @Test
+    // TODO: b/366136115 - Remove test for deprecated API when the API is fully removed.
+    @SuppressWarnings("deprecation")
     public void notifiesAllRequestOnCaptureFailed() {
         CountDownLatch latch = new CountDownLatch(3);
         List<CameraCaptureFailure> failureList = new ArrayList<>();
@@ -134,6 +166,39 @@
     }
 
     @Test
+    public void completeAllCaptureRequests_notifiesCaptureFailedToAllRequests() {
+        CountDownLatch latch = new CountDownLatch(3);
+        CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureFailed(int captureConfigId,
+                    @NonNull CameraCaptureFailure failure) {
+                latch.countDown();
+            }
+        }, new CameraCaptureCallback() {
+            @Override
+            public void onCaptureFailed(int captureConfigId,
+                    @NonNull CameraCaptureFailure failure) {
+                latch.countDown();
+            }
+        });
+        CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureFailed(int captureConfigId,
+                    @NonNull CameraCaptureFailure failure) {
+                latch.countDown();
+            }
+        });
+
+        mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
+        mCameraControl.completeAllCaptureRequests(CaptureResult.failedResult());
+
+        awaitLatch(latch);
+    }
+
+    @Test
+    // TODO: b/366136115 - Remove test for deprecated API when the API is fully removed.
+    @SuppressWarnings("deprecation")
     public void notifiesAllRequestOnCaptureCompleted() {
         CameraCaptureResult captureResult = new FakeCameraCaptureResult();
 
@@ -173,6 +238,82 @@
     }
 
     @Test
+    public void completeAllCaptureRequests_notifiesCaptureCompletedToAllRequests() {
+        FakeCameraCaptureResult captureResult = new FakeCameraCaptureResult();
+
+        CountDownLatch latch = new CountDownLatch(3);
+        List<CameraCaptureResult> resultList = new ArrayList<>();
+        CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+                latch.countDown();
+            }
+        }, new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+                latch.countDown();
+            }
+        });
+        CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+                latch.countDown();
+            }
+        });
+
+        mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
+        mCameraControl.completeAllCaptureRequests(CaptureResult.successfulResult(captureResult));
+
+        awaitLatch(latch);
+        assertThat(resultList).containsExactlyElementsIn(Arrays.asList(captureResult, captureResult,
+                captureResult));
+    }
+
+    @Test
+    public void submitCaptureResult_notifiesCaptureCompletedToAllCallbacksOfFirstRequest() {
+        FakeCameraCaptureResult captureResult = new FakeCameraCaptureResult();
+
+        CountDownLatch latch = new CountDownLatch(2);
+        List<CameraCaptureResult> resultList = new ArrayList<>();
+        CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+                latch.countDown();
+            }
+        }, new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+                latch.countDown();
+            }
+        });
+        CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
+            @Override
+            public void onCaptureCompleted(int captureConfigId,
+                    @NonNull CameraCaptureResult cameraCaptureResult) {
+                resultList.add(cameraCaptureResult);
+            }
+        });
+
+        mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
+                ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
+        mCameraControl.submitCaptureResult(CaptureResult.successfulResult(captureResult));
+
+        awaitLatch(latch);
+        assertThat(resultList).containsExactlyElementsIn(List.of(captureResult, captureResult));
+    }
+
+    @Test
     public void canUpdateFlashModeToOff() {
         mCameraControl.setFlashMode(ImageCapture.FLASH_MODE_OFF);
         assertThat(mCameraControl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_OFF);
@@ -264,7 +405,7 @@
     @Test
     public void futureCompletes_whenStillCaptureRequestsSubmittedAndSuccessNotified() {
         ListenableFuture<?> future = submitStillCaptureRequests();
-        mCameraControl.notifyAllRequestsOnCaptureCompleted(new FakeCameraCaptureResult());
+        mCameraControl.completeAllCaptureRequests(CaptureResult.successfulResult());
 
         try {
             future.get(3, TimeUnit.SECONDS);
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
index 3b491b0..f8a913b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
@@ -17,11 +17,6 @@
 
 import android.Manifest
 import android.content.Context
-import android.graphics.SurfaceTexture
-import android.os.Handler
-import android.os.HandlerThread
-import android.util.Log
-import android.view.Surface
 import androidx.camera.camera2.Camera2Config
 import androidx.camera.camera2.pipe.integration.CameraPipeConfig
 import androidx.camera.core.Camera
@@ -35,12 +30,11 @@
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCase
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.impl.AndroidUtil.skipVideoRecordingTestIfNotSupportedByEmulator
 import androidx.camera.testing.impl.CameraPipeConfigTestRule
 import androidx.camera.testing.impl.CameraUtil
-import androidx.camera.testing.impl.GLUtil
+import androidx.camera.testing.impl.SurfaceTextureProvider.createAutoDrainingSurfaceTextureProvider
 import androidx.camera.testing.impl.WakelockEmptyActivityRule
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
 import androidx.camera.testing.impl.video.AudioChecker
@@ -497,47 +491,9 @@
 
     class PreviewMonitor {
         private var countDown: CountDownLatch? = null
-        private val surfaceProvider =
-            Preview.SurfaceProvider { request ->
-                val lock = Any()
-                var surfaceTextureReleased = false
-                val surfaceTexture = SurfaceTexture(0)
-                surfaceTexture.setDefaultBufferSize(
-                    request.resolution.width,
-                    request.resolution.height
-                )
-                surfaceTexture.detachFromGLContext()
-                surfaceTexture.attachToGLContext(GLUtil.getTexIdFromGLContext())
-                val frameUpdateThread = HandlerThread("frameUpdateThread").apply { start() }
-
-                surfaceTexture.setOnFrameAvailableListener(
-                    {
-                        InstrumentationRegistry.getInstrumentation().runOnMainSync {
-                            synchronized(lock) {
-                                if (!surfaceTextureReleased) {
-                                    try {
-                                        surfaceTexture.updateTexImage()
-                                    } catch (e: IllegalStateException) {
-                                        Log.e(TAG, "updateTexImage failed!")
-                                    }
-                                }
-                            }
-                        }
-                        countDown?.countDown()
-                    },
-                    Handler(frameUpdateThread.getLooper())
-                )
-
-                val surface = Surface(surfaceTexture)
-                request.provideSurface(surface, CameraXExecutors.directExecutor()) {
-                    synchronized(lock) {
-                        surfaceTextureReleased = true
-                        surface.release()
-                        surfaceTexture.release()
-                        frameUpdateThread.quitSafely()
-                    }
-                }
-            }
+        private val surfaceProvider = createAutoDrainingSurfaceTextureProvider {
+            countDown?.countDown()
+        }
 
         fun getSurfaceProvider(): Preview.SurfaceProvider = surfaceProvider
 
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt
index af52c44..3bd6e9b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.camera.integration.core.fakecamera
 
+import android.Manifest
 import android.content.ContentValues
 import android.content.Context
 import android.os.Build
@@ -27,10 +28,13 @@
 import androidx.camera.testing.fakes.FakeAppConfig
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.successfulResult
+import androidx.camera.testing.impl.IgnoreProblematicDeviceRule
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
 import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
 import androidx.camera.testing.impl.fakes.FakeOnImageSavedCallback
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.rule.GrantPermissionRule
 import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import java.text.SimpleDateFormat
@@ -41,8 +45,8 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.junit.After
+import org.junit.Assume.assumeFalse
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
@@ -63,6 +67,14 @@
     val temporaryFolder =
         TemporaryFolder(ApplicationProvider.getApplicationContext<Context>().cacheDir)
 
+    // Required for MediaStore tests on some emulators
+    @get:Rule
+    val storagePermissionRule: GrantPermissionRule =
+        GrantPermissionRule.grant(
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE
+        )
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private lateinit var cameraProvider: ProcessCameraProvider
     private lateinit var camera: FakeCamera
@@ -96,18 +108,25 @@
 
     // Duplicate to ImageCaptureTest on core-test-app JVM tests, any change here may need to be
     // reflected there too
-    @Ignore("b/318314454")
     @Test
     fun canCreateBitmapFromTakenImage_whenImageCapturedCallbackIsUsed(): Unit = runBlocking {
-        val callback = FakeOnImageCapturedCallback()
+        assumeFalse(
+            "This emulator fails to create a bitmap from an android.media.Image instance" +
+                ", the emulator is known to have various issues and generally ignored in our tests",
+            IgnoreProblematicDeviceRule.isPixel2Api26Emulator
+        )
+
+        val callback = FakeOnImageCapturedCallback(closeImageOnSuccess = false)
+
         imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
+        cameraControl.submitCaptureResult(successfulResult())
+
         callback.awaitCapturesAndAssert(capturedImagesCount = 1)
-        callback.results.first().image.toBitmap()
+        assertThat(callback.results.first().image.toBitmap()).isNotNull()
     }
 
     // Duplicate to ImageCaptureTest on core-test-app JVM tests, any change here may need to be
     // reflected there too
-    @Ignore("b/318314454")
     @Test
     fun canFindImage_whenFileStorageAndImageSavedCallbackIsUsed(): Unit = runBlocking {
         val saveLocation = temporaryFolder.newFile()
@@ -119,6 +138,7 @@
             CameraXExecutors.directExecutor(),
             callback
         )
+        cameraControl.submitCaptureResult(successfulResult())
 
         callback.awaitCapturesAndAssert(capturedImagesCount = 1)
         assertThat(saveLocation.length()).isGreaterThan(previousLength)
@@ -126,16 +146,18 @@
 
     // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
     // need to be reflected there too
-    @Ignore("b/318314454")
     @Test
     fun canFindImage_whenMediaStoreAndImageSavedCallbackIsUsed(): Unit = runBlocking {
         val initialCount = getMediaStoreCameraXImageCount()
         val callback = FakeOnImageSavedCallback()
+
         imageCapture.takePicture(
             createMediaStoreOutputOptions(),
             CameraXExecutors.directExecutor(),
             callback
         )
+        cameraControl.submitCaptureResult(successfulResult())
+
         callback.awaitCapturesAndAssert(capturedImagesCount = 1)
         assertThat(getMediaStoreCameraXImageCount()).isEqualTo(initialCount + 1)
     }
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index aec2319..b8cdb7e 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -30,6 +30,7 @@
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG;
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR;
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_RAW;
+import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_RAW_JPEG;
 import static androidx.camera.core.ImageCapture.getImageCaptureCapabilities;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
 import static androidx.camera.integration.core.CameraXViewModel.getConfiguredCameraXCameraImplementation;
@@ -975,6 +976,7 @@
         }
     }
 
+    @SuppressLint("RestrictedApiAndroidX")
     private void setUpTakePictureButton() {
         mTakePicture.setOnClickListener(
                 new View.OnClickListener() {
@@ -985,47 +987,64 @@
                         mImageSavedIdlingResource.increment();
                         mStartCaptureTime = SystemClock.elapsedRealtime();
 
-                        ImageCapture.OutputFileOptions outputFileOptions =
-                                createOutputFileOptions(mImageOutputFormat);
-                        getImageCapture().takePicture(outputFileOptions,
-                                mImageCaptureExecutorService,
-                                new ImageCapture.OnImageSavedCallback() {
-                                    @Override
-                                    public void onImageSaved(
-                                            @NonNull ImageCapture.OutputFileResults
-                                                    outputFileResults) {
-                                        Log.d(TAG, "Saved image to "
-                                                + outputFileResults.getSavedUri());
-                                        try {
-                                            mImageSavedIdlingResource.decrement();
-                                        } catch (IllegalStateException e) {
-                                            Log.e(TAG, "Error: unexpected onImageSaved "
-                                                    + "callback received. Continuing.");
-                                        }
+                        ImageCapture.OnImageSavedCallback callback = new ImageCapture
+                                .OnImageSavedCallback() {
+                            @Override
+                            public void onImageSaved(
+                                    @NonNull ImageCapture.OutputFileResults
+                                            outputFileResults) {
+                                Log.d(TAG, "Saved image to "
+                                        + outputFileResults.getSavedUri());
+                                try {
+                                    mImageSavedIdlingResource.decrement();
+                                } catch (IllegalStateException e) {
+                                    Log.e(TAG, "Error: unexpected onImageSaved "
+                                            + "callback received. Continuing.");
+                                }
 
-                                        long duration =
-                                                SystemClock.elapsedRealtime() - mStartCaptureTime;
-                                        runOnUiThread(() -> Toast.makeText(CameraXActivity.this,
-                                                "Image captured in " + duration + " ms",
-                                                Toast.LENGTH_SHORT).show());
-                                        if (mSessionImagesUriSet != null) {
-                                            mSessionImagesUriSet.add(
-                                                    requireNonNull(
-                                                            outputFileResults.getSavedUri()));
-                                        }
-                                    }
+                                long duration =
+                                        SystemClock.elapsedRealtime()
+                                                - mStartCaptureTime;
+                                runOnUiThread(() -> Toast.makeText(CameraXActivity.this,
+                                        "Image captured in " + duration + " ms",
+                                        Toast.LENGTH_SHORT).show());
+                                if (mSessionImagesUriSet != null) {
+                                    mSessionImagesUriSet.add(
+                                            requireNonNull(
+                                                    outputFileResults.getSavedUri()));
+                                }
+                            }
 
-                                    @Override
-                                    public void onError(@NonNull ImageCaptureException exception) {
-                                        Log.e(TAG, "Failed to save image.", exception);
+                            @Override
+                            public void onError(
+                                    @NonNull ImageCaptureException exception) {
+                                Log.e(TAG, "Failed to save image.", exception);
 
-                                        mLastTakePictureErrorMessage =
-                                                getImageCaptureErrorMessage(exception);
-                                        if (!mImageSavedIdlingResource.isIdleNow()) {
-                                            mImageSavedIdlingResource.decrement();
-                                        }
-                                    }
-                                });
+                                mLastTakePictureErrorMessage =
+                                        getImageCaptureErrorMessage(exception);
+                                if (!mImageSavedIdlingResource.isIdleNow()) {
+                                    mImageSavedIdlingResource.decrement();
+                                }
+                            }
+                        };
+
+                        if (mImageOutputFormat == OUTPUT_FORMAT_RAW_JPEG) {
+                            ImageCapture.OutputFileOptions rawOutputFileOptions =
+                                    createOutputFileOptions(OUTPUT_FORMAT_RAW);
+                            ImageCapture.OutputFileOptions jpegOutputFileOptions =
+                                    createOutputFileOptions(OUTPUT_FORMAT_JPEG);
+                            getImageCapture().takePicture(
+                                    List.of(rawOutputFileOptions, jpegOutputFileOptions),
+                                    mImageCaptureExecutorService,
+                                    callback);
+                        } else {
+                            ImageCapture.OutputFileOptions outputFileOptions =
+                                    createOutputFileOptions(mImageOutputFormat);
+                            getImageCapture().takePicture(
+                                    outputFileOptions,
+                                    mImageCaptureExecutorService,
+                                    callback);
+                        }
                     }
                 });
     }
@@ -1058,9 +1077,9 @@
         contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
         contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimetype);
         return new ImageCapture.OutputFileOptions.Builder(
-                        getContentResolver(),
-                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                        contentValues).build();
+                getContentResolver(),
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                contentValues).build();
     }
 
 
@@ -2654,6 +2673,8 @@
             return "Ultra HDR";
         } else if (format == OUTPUT_FORMAT_RAW) {
             return "Raw";
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return "Raw + Jpeg";
         }
         return "?";
     }
@@ -2667,6 +2688,8 @@
             return "Ultra HDR";
         } else if (format == OUTPUT_FORMAT_RAW) {
             return "Raw";
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return "Raw + Jpeg";
         }
         return "Unknown format";
     }
@@ -2679,6 +2702,8 @@
             return 1;
         } else if (format == OUTPUT_FORMAT_RAW) {
             return 2;
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return 3;
         } else {
             throw new IllegalArgumentException("Undefined output format: " + format);
         }
@@ -2694,6 +2719,8 @@
                 return OUTPUT_FORMAT_JPEG_ULTRA_HDR;
             case 2:
                 return OUTPUT_FORMAT_RAW;
+            case 3:
+                return OUTPUT_FORMAT_RAW_JPEG;
             default:
                 throw new IllegalArgumentException("Undefined item id: " + itemId);
         }
@@ -2728,7 +2755,7 @@
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     private static int getCamera2LensFacing(@NonNull CameraInfo cameraInfo) {
         Integer lensFacing = Camera2CameraInfo.from(cameraInfo).getCameraCharacteristic(
-                    CameraCharacteristics.LENS_FACING);
+                CameraCharacteristics.LENS_FACING);
 
         return lensFacing == null ? CameraCharacteristics.LENS_FACING_BACK : lensFacing;
     }
diff --git a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 37609c1..b12b8d8 100644
--- a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -19,11 +19,15 @@
 import android.content.ContentValues
 import android.content.Context
 import android.os.Build
+import android.os.Looper
 import android.provider.MediaStore
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.cancelledResult
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.failedResult
+import androidx.camera.testing.imagecapture.CaptureResult.Companion.successfulResult
 import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
 import androidx.camera.testing.impl.fakes.FakeOnImageSavedCallback
 import androidx.camera.testing.rules.FakeCameraTestRule
@@ -33,15 +37,17 @@
 import java.util.Locale
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 
@@ -75,20 +81,111 @@
         assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue()
     }
 
-    // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
-    // need to be reflected there too
-    @Ignore("b/318314454")
     @Test
-    fun canCreateBitmapFromTakenImage_whenImageCapturedCallbackIsUsed(): Unit = runTest {
+    fun completesAllCaptures_whenMultiplePicsTakenConsecutivelyBeforeSubmittingResults(): Unit =
+        runBlocking {
+            val callback1 = FakeOnImageCapturedCallback()
+            val callback2 = FakeOnImageCapturedCallback()
+
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback1)
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback2)
+            cameraControl.submitCaptureResult(successfulResult())
+            cameraControl.submitCaptureResult(successfulResult())
+
+            // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+            shadowOf(Looper.getMainLooper()).idle()
+
+            callback1.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+            callback2.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+        }
+
+    @Test
+    fun completesAllCaptures_whenMultiplePicsTakenConsecutivelyAfterSubmittingResults(): Unit =
+        runBlocking {
+            val callback1 = FakeOnImageCapturedCallback()
+            val callback2 = FakeOnImageCapturedCallback()
+
+            cameraControl.submitCaptureResult(successfulResult())
+            cameraControl.submitCaptureResult(successfulResult())
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback1)
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback2)
+
+            // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+            shadowOf(Looper.getMainLooper()).idle()
+
+            callback1.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+            callback2.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+        }
+
+    @Test
+    fun completesFirstCapture_whenMultiplePicsTakenConsecutivelyBeforeSubmittingResult(): Unit =
+        runBlocking {
+            val callback1 = FakeOnImageCapturedCallback()
+            val callback2 = FakeOnImageCapturedCallback()
+
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback1)
+            imageCapture.takePicture(CameraXExecutors.directExecutor(), callback2)
+            cameraControl.submitCaptureResult(successfulResult())
+
+            // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+            shadowOf(Looper.getMainLooper()).idle()
+
+            callback1.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+            callback2.assertNoCapture(timeout = 100.milliseconds)
+        }
+
+    @Test
+    fun completesCaptureWithError_whenCaptureRequestsCancelled(): Unit = runBlocking {
         val callback = FakeOnImageCapturedCallback()
+
         imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
-        callback.awaitCapturesAndAssert(capturedImagesCount = 1)
-        callback.results.first().image.toBitmap()
+        cameraControl.submitCaptureResult(cancelledResult())
+
+        // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+        shadowOf(Looper.getMainLooper()).idle()
+
+        callback.awaitCapturesAndAssert(
+            timeout = 1.seconds,
+            errorsCount = 1,
+            capturedImagesCount = 0
+        )
+    }
+
+    @Test
+    fun completesCaptureWithError_whenCaptureRequestsFailed(): Unit = runBlocking {
+        val callback = FakeOnImageCapturedCallback()
+
+        imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
+        cameraControl.submitCaptureResult(failedResult())
+
+        // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+        shadowOf(Looper.getMainLooper()).idle()
+
+        callback.awaitCapturesAndAssert(
+            timeout = 1.seconds,
+            errorsCount = 1,
+            capturedImagesCount = 0
+        )
     }
 
     // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
     // need to be reflected there too
-    @Ignore("b/318314454")
+    @Test
+    fun canCreateBitmapFromTakenImage_whenImageCapturedCallbackIsUsed(): Unit = runTest {
+        val callback = FakeOnImageCapturedCallback()
+
+        imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
+        cameraControl.submitCaptureResult(successfulResult())
+
+        // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+        shadowOf(Looper.getMainLooper()).idle()
+
+        callback.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
+        assertThat(callback.results.first().image.toBitmap()).isNotNull()
+    }
+
+    // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+    // need to be reflected there too
     @Test
     fun canFindImage_whenFileStorageAndImageSavedCallbackIsUsed(): Unit = runTest {
         val saveLocation = temporaryFolder.newFile()
@@ -100,23 +197,32 @@
             CameraXExecutors.directExecutor(),
             callback
         )
+        cameraControl.submitCaptureResult(successfulResult())
 
-        callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+        // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+        shadowOf(Looper.getMainLooper()).idle()
+
+        callback.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
         assertThat(saveLocation.length()).isGreaterThan(previousLength)
     }
 
     // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
     // need to be reflected there too
-    @Ignore("b/318314454")
     @Test
     fun canFindFakeImageUri_whenMediaStoreAndImageSavedCallbackIsUsed(): Unit = runBlocking {
         val callback = FakeOnImageSavedCallback()
+
         imageCapture.takePicture(
             createMediaStoreOutputOptions(),
             CameraXExecutors.directExecutor(),
             callback
         )
-        callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+        cameraControl.submitCaptureResult(successfulResult())
+
+        // TODO: b/365571231 - Can we remove CameraX main thread dependencies using more fakes?
+        shadowOf(Looper.getMainLooper()).idle()
+
+        callback.awaitCapturesAndAssert(timeout = 1.seconds, capturedImagesCount = 1)
         assertThat(callback.results.first().savedUri).isNotNull()
     }
 
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index d7df27b..40eca5a 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -148,6 +148,8 @@
   }
 
   public final class DoubleListKt {
+    method public static inline androidx.collection.DoubleList buildDoubleList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.DoubleList buildDoubleList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
     method public static androidx.collection.DoubleList doubleListOf();
     method public static androidx.collection.DoubleList doubleListOf(double element1);
     method public static androidx.collection.DoubleList doubleListOf(double element1, double element2);
@@ -165,7 +167,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -198,6 +200,8 @@
   }
 
   public final class FloatFloatMapKt {
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1);
@@ -228,7 +232,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -261,6 +265,8 @@
   }
 
   public final class FloatIntMapKt {
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatIntMap emptyFloatIntMap();
     method public static androidx.collection.FloatIntMap floatIntMapOf();
     method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1);
@@ -330,6 +336,8 @@
   }
 
   public final class FloatListKt {
+    method public static inline androidx.collection.FloatList buildFloatList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatList buildFloatList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatList emptyFloatList();
     method public static androidx.collection.FloatList floatListOf();
     method public static androidx.collection.FloatList floatListOf(float element1);
@@ -347,7 +355,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -380,6 +388,8 @@
   }
 
   public final class FloatLongMapKt {
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatLongMap emptyFloatLongMap();
     method public static androidx.collection.FloatLongMap floatLongMapOf();
     method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1);
@@ -399,7 +409,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -432,6 +442,8 @@
   }
 
   public final class FloatObjectMapKt {
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.FloatObjectMap<V> emptyFloatObjectMap();
     method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf();
     method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1);
@@ -479,6 +491,8 @@
   }
 
   public final class FloatSetKt {
+    method public static inline androidx.collection.FloatSet buildFloatSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatSet buildFloatSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatSet emptyFloatSet();
     method public static androidx.collection.FloatSet floatSetOf();
     method public static androidx.collection.FloatSet floatSetOf(float element1);
@@ -496,7 +510,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -529,6 +543,8 @@
   }
 
   public final class IntFloatMapKt {
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntFloatMap emptyIntFloatMap();
     method public static androidx.collection.IntFloatMap intFloatMapOf();
     method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1);
@@ -548,7 +564,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -581,6 +597,8 @@
   }
 
   public final class IntIntMapKt {
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntIntMap emptyIntIntMap();
     method public static androidx.collection.IntIntMap intIntMapOf();
     method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1);
@@ -661,6 +679,8 @@
   }
 
   public final class IntListKt {
+    method public static inline androidx.collection.IntList buildIntList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntList buildIntList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
     method public static androidx.collection.IntList emptyIntList();
     method public static androidx.collection.IntList intListOf();
     method public static androidx.collection.IntList intListOf(int element1);
@@ -678,7 +698,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -711,6 +731,8 @@
   }
 
   public final class IntLongMapKt {
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntLongMap emptyIntLongMap();
     method public static androidx.collection.IntLongMap intLongMapOf();
     method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1);
@@ -730,7 +752,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -763,6 +785,8 @@
   }
 
   public final class IntObjectMapKt {
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.IntObjectMap<V> emptyIntObjectMap();
     method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf();
     method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1);
@@ -810,6 +834,8 @@
   }
 
   public final class IntSetKt {
+    method public static inline androidx.collection.IntSet buildIntSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntSet buildIntSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
     method public static androidx.collection.IntSet emptyIntSet();
     method public static androidx.collection.IntSet intSetOf();
     method public static androidx.collection.IntSet intSetOf(int element1);
@@ -827,7 +853,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -860,6 +886,8 @@
   }
 
   public final class LongFloatMapKt {
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongFloatMap emptyLongFloatMap();
     method public static androidx.collection.LongFloatMap longFloatMapOf();
     method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1);
@@ -879,7 +907,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -912,6 +940,8 @@
   }
 
   public final class LongIntMapKt {
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongIntMap emptyLongIntMap();
     method public static androidx.collection.LongIntMap longIntMapOf();
     method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1);
@@ -981,6 +1011,8 @@
   }
 
   public final class LongListKt {
+    method public static inline androidx.collection.LongList buildLongList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongList buildLongList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
     method public static androidx.collection.LongList emptyLongList();
     method public static androidx.collection.LongList longListOf();
     method public static androidx.collection.LongList longListOf(long element1);
@@ -998,7 +1030,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -1031,6 +1063,8 @@
   }
 
   public final class LongLongMapKt {
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongLongMap emptyLongLongMap();
     method public static androidx.collection.LongLongMap longLongMapOf();
     method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1);
@@ -1060,7 +1094,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -1093,6 +1127,8 @@
   }
 
   public final class LongObjectMapKt {
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.LongObjectMap<V> emptyLongObjectMap();
     method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf();
     method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1);
@@ -1140,6 +1176,8 @@
   }
 
   public final class LongSetKt {
+    method public static inline androidx.collection.LongSet buildLongSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongSet buildLongSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
     method public static androidx.collection.LongSet emptyLongSet();
     method public static androidx.collection.LongSet longSetOf();
     method public static androidx.collection.LongSet longSetOf(long element1);
@@ -1877,7 +1915,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -1910,6 +1948,8 @@
   }
 
   public final class ObjectFloatMapKt {
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectFloatMap<K> emptyObjectFloatMap();
     method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf();
     method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1);
@@ -1929,7 +1969,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -1962,6 +2002,8 @@
   }
 
   public final class ObjectIntMapKt {
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectIntMap<K> emptyObjectIntMap();
     method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf();
     method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1);
@@ -2048,7 +2090,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -2081,6 +2123,8 @@
   }
 
   public final class ObjectLongMapKt {
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectLongMap<K> emptyObjectLongMap();
     method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf();
     method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1);
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 8b56e9b..b740615 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -150,6 +150,8 @@
   }
 
   public final class DoubleListKt {
+    method public static inline androidx.collection.DoubleList buildDoubleList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.DoubleList buildDoubleList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
     method public static androidx.collection.DoubleList doubleListOf();
     method public static androidx.collection.DoubleList doubleListOf(double element1);
     method public static androidx.collection.DoubleList doubleListOf(double element1, double element2);
@@ -167,7 +169,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -205,6 +207,8 @@
   }
 
   public final class FloatFloatMapKt {
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1);
@@ -235,7 +239,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -273,6 +277,8 @@
   }
 
   public final class FloatIntMapKt {
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatIntMap emptyFloatIntMap();
     method public static androidx.collection.FloatIntMap floatIntMapOf();
     method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1);
@@ -344,6 +350,8 @@
   }
 
   public final class FloatListKt {
+    method public static inline androidx.collection.FloatList buildFloatList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatList buildFloatList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatList emptyFloatList();
     method public static androidx.collection.FloatList floatListOf();
     method public static androidx.collection.FloatList floatListOf(float element1);
@@ -361,7 +369,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -399,6 +407,8 @@
   }
 
   public final class FloatLongMapKt {
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatLongMap emptyFloatLongMap();
     method public static androidx.collection.FloatLongMap floatLongMapOf();
     method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1);
@@ -418,7 +428,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(float key);
+    method public final inline operator boolean contains(float key);
     method public final boolean containsKey(float key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -455,6 +465,8 @@
   }
 
   public final class FloatObjectMapKt {
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.FloatObjectMap<V> emptyFloatObjectMap();
     method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf();
     method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1);
@@ -505,6 +517,8 @@
   }
 
   public final class FloatSetKt {
+    method public static inline androidx.collection.FloatSet buildFloatSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatSet buildFloatSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatSet emptyFloatSet();
     method public static androidx.collection.FloatSet floatSetOf();
     method public static androidx.collection.FloatSet floatSetOf(float element1);
@@ -522,7 +536,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -560,6 +574,8 @@
   }
 
   public final class IntFloatMapKt {
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntFloatMap emptyIntFloatMap();
     method public static androidx.collection.IntFloatMap intFloatMapOf();
     method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1);
@@ -579,7 +595,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -617,6 +633,8 @@
   }
 
   public final class IntIntMapKt {
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntIntMap emptyIntIntMap();
     method public static androidx.collection.IntIntMap intIntMapOf();
     method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1);
@@ -699,6 +717,8 @@
   }
 
   public final class IntListKt {
+    method public static inline androidx.collection.IntList buildIntList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntList buildIntList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
     method public static androidx.collection.IntList emptyIntList();
     method public static androidx.collection.IntList intListOf();
     method public static androidx.collection.IntList intListOf(int element1);
@@ -716,7 +736,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -754,6 +774,8 @@
   }
 
   public final class IntLongMapKt {
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntLongMap emptyIntLongMap();
     method public static androidx.collection.IntLongMap intLongMapOf();
     method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1);
@@ -773,7 +795,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(int key);
+    method public final inline operator boolean contains(int key);
     method public final boolean containsKey(int key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -810,6 +832,8 @@
   }
 
   public final class IntObjectMapKt {
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.IntObjectMap<V> emptyIntObjectMap();
     method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf();
     method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1);
@@ -860,6 +884,8 @@
   }
 
   public final class IntSetKt {
+    method public static inline androidx.collection.IntSet buildIntSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntSet buildIntSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
     method public static androidx.collection.IntSet emptyIntSet();
     method public static androidx.collection.IntSet intSetOf();
     method public static androidx.collection.IntSet intSetOf(int element1);
@@ -877,7 +903,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -915,6 +941,8 @@
   }
 
   public final class LongFloatMapKt {
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongFloatMap emptyLongFloatMap();
     method public static androidx.collection.LongFloatMap longFloatMapOf();
     method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1);
@@ -934,7 +962,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -972,6 +1000,8 @@
   }
 
   public final class LongIntMapKt {
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongIntMap emptyLongIntMap();
     method public static androidx.collection.LongIntMap longIntMapOf();
     method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1);
@@ -1043,6 +1073,8 @@
   }
 
   public final class LongListKt {
+    method public static inline androidx.collection.LongList buildLongList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongList buildLongList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
     method public static androidx.collection.LongList emptyLongList();
     method public static androidx.collection.LongList longListOf();
     method public static androidx.collection.LongList longListOf(long element1);
@@ -1060,7 +1092,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -1098,6 +1130,8 @@
   }
 
   public final class LongLongMapKt {
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongLongMap emptyLongLongMap();
     method public static androidx.collection.LongLongMap longLongMapOf();
     method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1);
@@ -1127,7 +1161,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
-    method public final operator boolean contains(long key);
+    method public final inline operator boolean contains(long key);
     method public final boolean containsKey(long key);
     method public final boolean containsValue(V value);
     method public final int count();
@@ -1164,6 +1198,8 @@
   }
 
   public final class LongObjectMapKt {
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
     method public static <V> androidx.collection.LongObjectMap<V> emptyLongObjectMap();
     method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf();
     method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1);
@@ -1214,6 +1250,8 @@
   }
 
   public final class LongSetKt {
+    method public static inline androidx.collection.LongSet buildLongSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongSet buildLongSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
     method public static androidx.collection.LongSet emptyLongSet();
     method public static androidx.collection.LongSet longSetOf();
     method public static androidx.collection.LongSet longSetOf(long element1);
@@ -1970,7 +2008,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(float value);
     method public final int count();
@@ -2008,6 +2046,8 @@
   }
 
   public final class ObjectFloatMapKt {
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectFloatMap<K> emptyObjectFloatMap();
     method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf();
     method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1);
@@ -2027,7 +2067,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(int value);
     method public final int count();
@@ -2065,6 +2105,8 @@
   }
 
   public final class ObjectIntMapKt {
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectIntMap<K> emptyObjectIntMap();
     method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf();
     method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1);
@@ -2153,7 +2195,7 @@
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
-    method public final operator boolean contains(K key);
+    method public final inline operator boolean contains(K key);
     method public final boolean containsKey(K key);
     method public final boolean containsValue(long value);
     method public final int count();
@@ -2191,6 +2233,8 @@
   }
 
   public final class ObjectLongMapKt {
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
     method public static <K> androidx.collection.ObjectLongMap<K> emptyObjectLongMap();
     method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf();
     method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1);
diff --git a/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index 38ae0962..012c700f 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -1042,7 +1042,6 @@
         final fun <set-values>(kotlin/Array<kotlin/Any?>) // androidx.collection/FloatObjectMap.values.<set-values>|<set-values>(kotlin.Array<kotlin.Any?>){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/FloatObjectMap.any|any(){}[0]
-    final fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatObjectMap.contains|contains(kotlin.Float){}[0]
     final fun containsKey(kotlin/Float): kotlin/Boolean // androidx.collection/FloatObjectMap.containsKey|containsKey(kotlin.Float){}[0]
     final fun containsValue(#A): kotlin/Boolean // androidx.collection/FloatObjectMap.containsValue|containsValue(1:0){}[0]
     final fun count(): kotlin/Int // androidx.collection/FloatObjectMap.count|count(){}[0]
@@ -1054,6 +1053,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/FloatObjectMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Float, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatObjectMap.all|all(kotlin.Function2<kotlin.Float,1:0,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Float, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatObjectMap.any|any(kotlin.Function2<kotlin.Float,1:0,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatObjectMap.contains|contains(kotlin.Float){}[0]
     final inline fun count(kotlin/Function2<kotlin/Float, #A, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatObjectMap.count|count(kotlin.Function2<kotlin.Float,1:0,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Float, #A, kotlin/Unit>) // androidx.collection/FloatObjectMap.forEach|forEach(kotlin.Function2<kotlin.Float,1:0,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/FloatObjectMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1085,7 +1085,6 @@
         final fun <set-values>(kotlin/Array<kotlin/Any?>) // androidx.collection/IntObjectMap.values.<set-values>|<set-values>(kotlin.Array<kotlin.Any?>){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/IntObjectMap.any|any(){}[0]
-    final fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntObjectMap.contains|contains(kotlin.Int){}[0]
     final fun containsKey(kotlin/Int): kotlin/Boolean // androidx.collection/IntObjectMap.containsKey|containsKey(kotlin.Int){}[0]
     final fun containsValue(#A): kotlin/Boolean // androidx.collection/IntObjectMap.containsValue|containsValue(1:0){}[0]
     final fun count(): kotlin/Int // androidx.collection/IntObjectMap.count|count(){}[0]
@@ -1097,6 +1096,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/IntObjectMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Int, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntObjectMap.all|all(kotlin.Function2<kotlin.Int,1:0,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Int, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntObjectMap.any|any(kotlin.Function2<kotlin.Int,1:0,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntObjectMap.contains|contains(kotlin.Int){}[0]
     final inline fun count(kotlin/Function2<kotlin/Int, #A, kotlin/Boolean>): kotlin/Int // androidx.collection/IntObjectMap.count|count(kotlin.Function2<kotlin.Int,1:0,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Int, #A, kotlin/Unit>) // androidx.collection/IntObjectMap.forEach|forEach(kotlin.Function2<kotlin.Int,1:0,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/IntObjectMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1128,7 +1128,6 @@
         final fun <set-values>(kotlin/Array<kotlin/Any?>) // androidx.collection/LongObjectMap.values.<set-values>|<set-values>(kotlin.Array<kotlin.Any?>){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/LongObjectMap.any|any(){}[0]
-    final fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongObjectMap.contains|contains(kotlin.Long){}[0]
     final fun containsKey(kotlin/Long): kotlin/Boolean // androidx.collection/LongObjectMap.containsKey|containsKey(kotlin.Long){}[0]
     final fun containsValue(#A): kotlin/Boolean // androidx.collection/LongObjectMap.containsValue|containsValue(1:0){}[0]
     final fun count(): kotlin/Int // androidx.collection/LongObjectMap.count|count(){}[0]
@@ -1140,6 +1139,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/LongObjectMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Long, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongObjectMap.all|all(kotlin.Function2<kotlin.Long,1:0,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Long, #A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongObjectMap.any|any(kotlin.Function2<kotlin.Long,1:0,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongObjectMap.contains|contains(kotlin.Long){}[0]
     final inline fun count(kotlin/Function2<kotlin/Long, #A, kotlin/Boolean>): kotlin/Int // androidx.collection/LongObjectMap.count|count(kotlin.Function2<kotlin.Long,1:0,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Long, #A, kotlin/Unit>) // androidx.collection/LongObjectMap.forEach|forEach(kotlin.Function2<kotlin.Long,1:0,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/LongObjectMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1171,7 +1171,6 @@
         final fun <set-values>(kotlin/FloatArray) // androidx.collection/ObjectFloatMap.values.<set-values>|<set-values>(kotlin.FloatArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/ObjectFloatMap.any|any(){}[0]
-    final fun contains(#A): kotlin/Boolean // androidx.collection/ObjectFloatMap.contains|contains(1:0){}[0]
     final fun containsKey(#A): kotlin/Boolean // androidx.collection/ObjectFloatMap.containsKey|containsKey(1:0){}[0]
     final fun containsValue(kotlin/Float): kotlin/Boolean // androidx.collection/ObjectFloatMap.containsValue|containsValue(kotlin.Float){}[0]
     final fun count(): kotlin/Int // androidx.collection/ObjectFloatMap.count|count(){}[0]
@@ -1184,6 +1183,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/ObjectFloatMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<#A, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectFloatMap.all|all(kotlin.Function2<1:0,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<#A, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectFloatMap.any|any(kotlin.Function2<1:0,kotlin.Float,kotlin.Boolean>){}[0]
+    final inline fun contains(#A): kotlin/Boolean // androidx.collection/ObjectFloatMap.contains|contains(1:0){}[0]
     final inline fun count(kotlin/Function2<#A, kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/ObjectFloatMap.count|count(kotlin.Function2<1:0,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<#A, kotlin/Float, kotlin/Unit>) // androidx.collection/ObjectFloatMap.forEach|forEach(kotlin.Function2<1:0,kotlin.Float,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/ObjectFloatMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1215,7 +1215,6 @@
         final fun <set-values>(kotlin/IntArray) // androidx.collection/ObjectIntMap.values.<set-values>|<set-values>(kotlin.IntArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/ObjectIntMap.any|any(){}[0]
-    final fun contains(#A): kotlin/Boolean // androidx.collection/ObjectIntMap.contains|contains(1:0){}[0]
     final fun containsKey(#A): kotlin/Boolean // androidx.collection/ObjectIntMap.containsKey|containsKey(1:0){}[0]
     final fun containsValue(kotlin/Int): kotlin/Boolean // androidx.collection/ObjectIntMap.containsValue|containsValue(kotlin.Int){}[0]
     final fun count(): kotlin/Int // androidx.collection/ObjectIntMap.count|count(){}[0]
@@ -1228,6 +1227,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/ObjectIntMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<#A, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectIntMap.all|all(kotlin.Function2<1:0,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<#A, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectIntMap.any|any(kotlin.Function2<1:0,kotlin.Int,kotlin.Boolean>){}[0]
+    final inline fun contains(#A): kotlin/Boolean // androidx.collection/ObjectIntMap.contains|contains(1:0){}[0]
     final inline fun count(kotlin/Function2<#A, kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/ObjectIntMap.count|count(kotlin.Function2<1:0,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<#A, kotlin/Int, kotlin/Unit>) // androidx.collection/ObjectIntMap.forEach|forEach(kotlin.Function2<1:0,kotlin.Int,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/ObjectIntMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1319,7 +1319,6 @@
         final fun <set-values>(kotlin/LongArray) // androidx.collection/ObjectLongMap.values.<set-values>|<set-values>(kotlin.LongArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/ObjectLongMap.any|any(){}[0]
-    final fun contains(#A): kotlin/Boolean // androidx.collection/ObjectLongMap.contains|contains(1:0){}[0]
     final fun containsKey(#A): kotlin/Boolean // androidx.collection/ObjectLongMap.containsKey|containsKey(1:0){}[0]
     final fun containsValue(kotlin/Long): kotlin/Boolean // androidx.collection/ObjectLongMap.containsValue|containsValue(kotlin.Long){}[0]
     final fun count(): kotlin/Int // androidx.collection/ObjectLongMap.count|count(){}[0]
@@ -1332,6 +1331,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/ObjectLongMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<#A, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectLongMap.all|all(kotlin.Function2<1:0,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<#A, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/ObjectLongMap.any|any(kotlin.Function2<1:0,kotlin.Long,kotlin.Boolean>){}[0]
+    final inline fun contains(#A): kotlin/Boolean // androidx.collection/ObjectLongMap.contains|contains(1:0){}[0]
     final inline fun count(kotlin/Function2<#A, kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/ObjectLongMap.count|count(kotlin.Function2<1:0,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<#A, kotlin/Long, kotlin/Unit>) // androidx.collection/ObjectLongMap.forEach|forEach(kotlin.Function2<1:0,kotlin.Long,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/ObjectLongMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1504,7 +1504,6 @@
         final fun <set-values>(kotlin/FloatArray) // androidx.collection/FloatFloatMap.values.<set-values>|<set-values>(kotlin.FloatArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/FloatFloatMap.any|any(){}[0]
-    final fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatFloatMap.contains|contains(kotlin.Float){}[0]
     final fun containsKey(kotlin/Float): kotlin/Boolean // androidx.collection/FloatFloatMap.containsKey|containsKey(kotlin.Float){}[0]
     final fun containsValue(kotlin/Float): kotlin/Boolean // androidx.collection/FloatFloatMap.containsValue|containsValue(kotlin.Float){}[0]
     final fun count(): kotlin/Int // androidx.collection/FloatFloatMap.count|count(){}[0]
@@ -1517,6 +1516,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/FloatFloatMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Float, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatFloatMap.all|all(kotlin.Function2<kotlin.Float,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Float, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatFloatMap.any|any(kotlin.Function2<kotlin.Float,kotlin.Float,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatFloatMap.contains|contains(kotlin.Float){}[0]
     final inline fun count(kotlin/Function2<kotlin/Float, kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatFloatMap.count|count(kotlin.Function2<kotlin.Float,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Float, kotlin/Float, kotlin/Unit>) // androidx.collection/FloatFloatMap.forEach|forEach(kotlin.Function2<kotlin.Float,kotlin.Float,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/FloatFloatMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1548,7 +1548,6 @@
         final fun <set-values>(kotlin/IntArray) // androidx.collection/FloatIntMap.values.<set-values>|<set-values>(kotlin.IntArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/FloatIntMap.any|any(){}[0]
-    final fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatIntMap.contains|contains(kotlin.Float){}[0]
     final fun containsKey(kotlin/Float): kotlin/Boolean // androidx.collection/FloatIntMap.containsKey|containsKey(kotlin.Float){}[0]
     final fun containsValue(kotlin/Int): kotlin/Boolean // androidx.collection/FloatIntMap.containsValue|containsValue(kotlin.Int){}[0]
     final fun count(): kotlin/Int // androidx.collection/FloatIntMap.count|count(){}[0]
@@ -1561,6 +1560,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/FloatIntMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Float, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatIntMap.all|all(kotlin.Function2<kotlin.Float,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Float, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatIntMap.any|any(kotlin.Function2<kotlin.Float,kotlin.Int,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatIntMap.contains|contains(kotlin.Float){}[0]
     final inline fun count(kotlin/Function2<kotlin/Float, kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatIntMap.count|count(kotlin.Function2<kotlin.Float,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Float, kotlin/Int, kotlin/Unit>) // androidx.collection/FloatIntMap.forEach|forEach(kotlin.Function2<kotlin.Float,kotlin.Int,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/FloatIntMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1646,7 +1646,6 @@
         final fun <set-values>(kotlin/LongArray) // androidx.collection/FloatLongMap.values.<set-values>|<set-values>(kotlin.LongArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/FloatLongMap.any|any(){}[0]
-    final fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatLongMap.contains|contains(kotlin.Float){}[0]
     final fun containsKey(kotlin/Float): kotlin/Boolean // androidx.collection/FloatLongMap.containsKey|containsKey(kotlin.Float){}[0]
     final fun containsValue(kotlin/Long): kotlin/Boolean // androidx.collection/FloatLongMap.containsValue|containsValue(kotlin.Long){}[0]
     final fun count(): kotlin/Int // androidx.collection/FloatLongMap.count|count(){}[0]
@@ -1659,6 +1658,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/FloatLongMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Float, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatLongMap.all|all(kotlin.Function2<kotlin.Float,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Float, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatLongMap.any|any(kotlin.Function2<kotlin.Float,kotlin.Long,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatLongMap.contains|contains(kotlin.Float){}[0]
     final inline fun count(kotlin/Function2<kotlin/Float, kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatLongMap.count|count(kotlin.Function2<kotlin.Float,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Float, kotlin/Long, kotlin/Unit>) // androidx.collection/FloatLongMap.forEach|forEach(kotlin.Function2<kotlin.Float,kotlin.Long,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/FloatLongMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1725,7 +1725,6 @@
         final fun <set-values>(kotlin/FloatArray) // androidx.collection/IntFloatMap.values.<set-values>|<set-values>(kotlin.FloatArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/IntFloatMap.any|any(){}[0]
-    final fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntFloatMap.contains|contains(kotlin.Int){}[0]
     final fun containsKey(kotlin/Int): kotlin/Boolean // androidx.collection/IntFloatMap.containsKey|containsKey(kotlin.Int){}[0]
     final fun containsValue(kotlin/Float): kotlin/Boolean // androidx.collection/IntFloatMap.containsValue|containsValue(kotlin.Float){}[0]
     final fun count(): kotlin/Int // androidx.collection/IntFloatMap.count|count(){}[0]
@@ -1738,6 +1737,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/IntFloatMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Int, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntFloatMap.all|all(kotlin.Function2<kotlin.Int,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Int, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntFloatMap.any|any(kotlin.Function2<kotlin.Int,kotlin.Float,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntFloatMap.contains|contains(kotlin.Int){}[0]
     final inline fun count(kotlin/Function2<kotlin/Int, kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/IntFloatMap.count|count(kotlin.Function2<kotlin.Int,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Int, kotlin/Float, kotlin/Unit>) // androidx.collection/IntFloatMap.forEach|forEach(kotlin.Function2<kotlin.Int,kotlin.Float,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/IntFloatMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1769,7 +1769,6 @@
         final fun <set-values>(kotlin/IntArray) // androidx.collection/IntIntMap.values.<set-values>|<set-values>(kotlin.IntArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/IntIntMap.any|any(){}[0]
-    final fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntIntMap.contains|contains(kotlin.Int){}[0]
     final fun containsKey(kotlin/Int): kotlin/Boolean // androidx.collection/IntIntMap.containsKey|containsKey(kotlin.Int){}[0]
     final fun containsValue(kotlin/Int): kotlin/Boolean // androidx.collection/IntIntMap.containsValue|containsValue(kotlin.Int){}[0]
     final fun count(): kotlin/Int // androidx.collection/IntIntMap.count|count(){}[0]
@@ -1782,6 +1781,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/IntIntMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntIntMap.all|all(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntIntMap.any|any(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntIntMap.contains|contains(kotlin.Int){}[0]
     final inline fun count(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntIntMap.count|count(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // androidx.collection/IntIntMap.forEach|forEach(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/IntIntMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1867,7 +1867,6 @@
         final fun <set-values>(kotlin/LongArray) // androidx.collection/IntLongMap.values.<set-values>|<set-values>(kotlin.LongArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/IntLongMap.any|any(){}[0]
-    final fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntLongMap.contains|contains(kotlin.Int){}[0]
     final fun containsKey(kotlin/Int): kotlin/Boolean // androidx.collection/IntLongMap.containsKey|containsKey(kotlin.Int){}[0]
     final fun containsValue(kotlin/Long): kotlin/Boolean // androidx.collection/IntLongMap.containsValue|containsValue(kotlin.Long){}[0]
     final fun count(): kotlin/Int // androidx.collection/IntLongMap.count|count(){}[0]
@@ -1880,6 +1879,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/IntLongMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Int, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntLongMap.all|all(kotlin.Function2<kotlin.Int,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Int, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntLongMap.any|any(kotlin.Function2<kotlin.Int,kotlin.Long,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntLongMap.contains|contains(kotlin.Int){}[0]
     final inline fun count(kotlin/Function2<kotlin/Int, kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/IntLongMap.count|count(kotlin.Function2<kotlin.Int,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Int, kotlin/Long, kotlin/Unit>) // androidx.collection/IntLongMap.forEach|forEach(kotlin.Function2<kotlin.Int,kotlin.Long,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/IntLongMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1946,7 +1946,6 @@
         final fun <set-values>(kotlin/FloatArray) // androidx.collection/LongFloatMap.values.<set-values>|<set-values>(kotlin.FloatArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/LongFloatMap.any|any(){}[0]
-    final fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongFloatMap.contains|contains(kotlin.Long){}[0]
     final fun containsKey(kotlin/Long): kotlin/Boolean // androidx.collection/LongFloatMap.containsKey|containsKey(kotlin.Long){}[0]
     final fun containsValue(kotlin/Float): kotlin/Boolean // androidx.collection/LongFloatMap.containsValue|containsValue(kotlin.Float){}[0]
     final fun count(): kotlin/Int // androidx.collection/LongFloatMap.count|count(){}[0]
@@ -1959,6 +1958,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/LongFloatMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Long, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongFloatMap.all|all(kotlin.Function2<kotlin.Long,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Long, kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongFloatMap.any|any(kotlin.Function2<kotlin.Long,kotlin.Float,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongFloatMap.contains|contains(kotlin.Long){}[0]
     final inline fun count(kotlin/Function2<kotlin/Long, kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/LongFloatMap.count|count(kotlin.Function2<kotlin.Long,kotlin.Float,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Long, kotlin/Float, kotlin/Unit>) // androidx.collection/LongFloatMap.forEach|forEach(kotlin.Function2<kotlin.Long,kotlin.Float,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/LongFloatMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -1990,7 +1990,6 @@
         final fun <set-values>(kotlin/IntArray) // androidx.collection/LongIntMap.values.<set-values>|<set-values>(kotlin.IntArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/LongIntMap.any|any(){}[0]
-    final fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongIntMap.contains|contains(kotlin.Long){}[0]
     final fun containsKey(kotlin/Long): kotlin/Boolean // androidx.collection/LongIntMap.containsKey|containsKey(kotlin.Long){}[0]
     final fun containsValue(kotlin/Int): kotlin/Boolean // androidx.collection/LongIntMap.containsValue|containsValue(kotlin.Int){}[0]
     final fun count(): kotlin/Int // androidx.collection/LongIntMap.count|count(){}[0]
@@ -2003,6 +2002,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/LongIntMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Long, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongIntMap.all|all(kotlin.Function2<kotlin.Long,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Long, kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongIntMap.any|any(kotlin.Function2<kotlin.Long,kotlin.Int,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongIntMap.contains|contains(kotlin.Long){}[0]
     final inline fun count(kotlin/Function2<kotlin/Long, kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/LongIntMap.count|count(kotlin.Function2<kotlin.Long,kotlin.Int,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Long, kotlin/Int, kotlin/Unit>) // androidx.collection/LongIntMap.forEach|forEach(kotlin.Function2<kotlin.Long,kotlin.Int,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/LongIntMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -2088,7 +2088,6 @@
         final fun <set-values>(kotlin/LongArray) // androidx.collection/LongLongMap.values.<set-values>|<set-values>(kotlin.LongArray){}[0]
 
     final fun any(): kotlin/Boolean // androidx.collection/LongLongMap.any|any(){}[0]
-    final fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongLongMap.contains|contains(kotlin.Long){}[0]
     final fun containsKey(kotlin/Long): kotlin/Boolean // androidx.collection/LongLongMap.containsKey|containsKey(kotlin.Long){}[0]
     final fun containsValue(kotlin/Long): kotlin/Boolean // androidx.collection/LongLongMap.containsValue|containsValue(kotlin.Long){}[0]
     final fun count(): kotlin/Int // androidx.collection/LongLongMap.count|count(){}[0]
@@ -2101,6 +2100,7 @@
     final fun none(): kotlin/Boolean // androidx.collection/LongLongMap.none|none(){}[0]
     final inline fun all(kotlin/Function2<kotlin/Long, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongLongMap.all|all(kotlin.Function2<kotlin.Long,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun any(kotlin/Function2<kotlin/Long, kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongLongMap.any|any(kotlin.Function2<kotlin.Long,kotlin.Long,kotlin.Boolean>){}[0]
+    final inline fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongLongMap.contains|contains(kotlin.Long){}[0]
     final inline fun count(kotlin/Function2<kotlin/Long, kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/LongLongMap.count|count(kotlin.Function2<kotlin.Long,kotlin.Long,kotlin.Boolean>){}[0]
     final inline fun forEach(kotlin/Function2<kotlin/Long, kotlin/Long, kotlin/Unit>) // androidx.collection/LongLongMap.forEach|forEach(kotlin.Function2<kotlin.Long,kotlin.Long,kotlin.Unit>){}[0]
     final inline fun forEachIndexed(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/LongLongMap.forEachIndexed|forEachIndexed(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
@@ -2493,9 +2493,53 @@
 final inline fun <#A: kotlin/Any?> (androidx.collection/SparseArrayCompat<#A>).androidx.collection/isNotEmpty(): kotlin/Boolean // androidx.collection/isNotEmpty|[email protected]<0:0>(){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> (androidx.collection/SparseArrayCompat<#A>).androidx.collection/set(kotlin/Int, #A) // androidx.collection/set|[email protected]<0:0>(kotlin.Int;0:0){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/arraySetOf(): androidx.collection/ArraySet<#A> // androidx.collection/arraySetOf|arraySetOf(){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildFloatObjectMap(kotlin/Function1<androidx.collection/MutableFloatObjectMap<#A>, kotlin/Unit>): androidx.collection/FloatObjectMap<#A> // androidx.collection/buildFloatObjectMap|buildFloatObjectMap(kotlin.Function1<androidx.collection.MutableFloatObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildFloatObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatObjectMap<#A>, kotlin/Unit>): androidx.collection/FloatObjectMap<#A> // androidx.collection/buildFloatObjectMap|buildFloatObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildIntObjectMap(kotlin/Function1<androidx.collection/MutableIntObjectMap<#A>, kotlin/Unit>): androidx.collection/IntObjectMap<#A> // androidx.collection/buildIntObjectMap|buildIntObjectMap(kotlin.Function1<androidx.collection.MutableIntObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildIntObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntObjectMap<#A>, kotlin/Unit>): androidx.collection/IntObjectMap<#A> // androidx.collection/buildIntObjectMap|buildIntObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildLongObjectMap(kotlin/Function1<androidx.collection/MutableLongObjectMap<#A>, kotlin/Unit>): androidx.collection/LongObjectMap<#A> // androidx.collection/buildLongObjectMap|buildLongObjectMap(kotlin.Function1<androidx.collection.MutableLongObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildLongObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongObjectMap<#A>, kotlin/Unit>): androidx.collection/LongObjectMap<#A> // androidx.collection/buildLongObjectMap|buildLongObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectFloatMap(kotlin/Function1<androidx.collection/MutableObjectFloatMap<#A>, kotlin/Unit>): androidx.collection/ObjectFloatMap<#A> // androidx.collection/buildObjectFloatMap|buildObjectFloatMap(kotlin.Function1<androidx.collection.MutableObjectFloatMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectFloatMap<#A>, kotlin/Unit>): androidx.collection/ObjectFloatMap<#A> // androidx.collection/buildObjectFloatMap|buildObjectFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectFloatMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectIntMap(kotlin/Function1<androidx.collection/MutableObjectIntMap<#A>, kotlin/Unit>): androidx.collection/ObjectIntMap<#A> // androidx.collection/buildObjectIntMap|buildObjectIntMap(kotlin.Function1<androidx.collection.MutableObjectIntMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectIntMap<#A>, kotlin/Unit>): androidx.collection/ObjectIntMap<#A> // androidx.collection/buildObjectIntMap|buildObjectIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectIntMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectLongMap(kotlin/Function1<androidx.collection/MutableObjectLongMap<#A>, kotlin/Unit>): androidx.collection/ObjectLongMap<#A> // androidx.collection/buildObjectLongMap|buildObjectLongMap(kotlin.Function1<androidx.collection.MutableObjectLongMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectLongMap<#A>, kotlin/Unit>): androidx.collection/ObjectLongMap<#A> // androidx.collection/buildObjectLongMap|buildObjectLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectLongMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/mutableObjectListOf(): androidx.collection/MutableObjectList<#A> // androidx.collection/mutableObjectListOf|mutableObjectListOf(){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/mutableObjectListOf(kotlin/Array<out #A>...): androidx.collection/MutableObjectList<#A> // androidx.collection/mutableObjectListOf|mutableObjectListOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
 final inline fun androidx.collection.internal/floatFromBits(kotlin/Int): kotlin/Float // androidx.collection.internal/floatFromBits|floatFromBits(kotlin.Int){}[0]
+final inline fun androidx.collection/buildDoubleList(kotlin/Function1<androidx.collection/MutableDoubleList, kotlin/Unit>): androidx.collection/DoubleList // androidx.collection/buildDoubleList|buildDoubleList(kotlin.Function1<androidx.collection.MutableDoubleList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildDoubleList(kotlin/Int, kotlin/Function1<androidx.collection/MutableDoubleList, kotlin/Unit>): androidx.collection/DoubleList // androidx.collection/buildDoubleList|buildDoubleList(kotlin.Int;kotlin.Function1<androidx.collection.MutableDoubleList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatFloatMap(kotlin/Function1<androidx.collection/MutableFloatFloatMap, kotlin/Unit>): androidx.collection/FloatFloatMap // androidx.collection/buildFloatFloatMap|buildFloatFloatMap(kotlin.Function1<androidx.collection.MutableFloatFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatFloatMap, kotlin/Unit>): androidx.collection/FloatFloatMap // androidx.collection/buildFloatFloatMap|buildFloatFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatIntMap(kotlin/Function1<androidx.collection/MutableFloatIntMap, kotlin/Unit>): androidx.collection/FloatIntMap // androidx.collection/buildFloatIntMap|buildFloatIntMap(kotlin.Function1<androidx.collection.MutableFloatIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatIntMap, kotlin/Unit>): androidx.collection/FloatIntMap // androidx.collection/buildFloatIntMap|buildFloatIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatList(kotlin/Function1<androidx.collection/MutableFloatList, kotlin/Unit>): androidx.collection/FloatList // androidx.collection/buildFloatList|buildFloatList(kotlin.Function1<androidx.collection.MutableFloatList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatList(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatList, kotlin/Unit>): androidx.collection/FloatList // androidx.collection/buildFloatList|buildFloatList(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatLongMap(kotlin/Function1<androidx.collection/MutableFloatLongMap, kotlin/Unit>): androidx.collection/FloatLongMap // androidx.collection/buildFloatLongMap|buildFloatLongMap(kotlin.Function1<androidx.collection.MutableFloatLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatLongMap, kotlin/Unit>): androidx.collection/FloatLongMap // androidx.collection/buildFloatLongMap|buildFloatLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatSet(kotlin/Function1<androidx.collection/MutableFloatSet, kotlin/Unit>): androidx.collection/FloatSet // androidx.collection/buildFloatSet|buildFloatSet(kotlin.Function1<androidx.collection.MutableFloatSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatSet, kotlin/Unit>): androidx.collection/FloatSet // androidx.collection/buildFloatSet|buildFloatSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntFloatMap(kotlin/Function1<androidx.collection/MutableIntFloatMap, kotlin/Unit>): androidx.collection/IntFloatMap // androidx.collection/buildIntFloatMap|buildIntFloatMap(kotlin.Function1<androidx.collection.MutableIntFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntFloatMap, kotlin/Unit>): androidx.collection/IntFloatMap // androidx.collection/buildIntFloatMap|buildIntFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntIntMap(kotlin/Function1<androidx.collection/MutableIntIntMap, kotlin/Unit>): androidx.collection/IntIntMap // androidx.collection/buildIntIntMap|buildIntIntMap(kotlin.Function1<androidx.collection.MutableIntIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntIntMap, kotlin/Unit>): androidx.collection/IntIntMap // androidx.collection/buildIntIntMap|buildIntIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntList(kotlin/Function1<androidx.collection/MutableIntList, kotlin/Unit>): androidx.collection/IntList // androidx.collection/buildIntList|buildIntList(kotlin.Function1<androidx.collection.MutableIntList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntList(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntList, kotlin/Unit>): androidx.collection/IntList // androidx.collection/buildIntList|buildIntList(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntLongMap(kotlin/Function1<androidx.collection/MutableIntLongMap, kotlin/Unit>): androidx.collection/IntLongMap // androidx.collection/buildIntLongMap|buildIntLongMap(kotlin.Function1<androidx.collection.MutableIntLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntLongMap, kotlin/Unit>): androidx.collection/IntLongMap // androidx.collection/buildIntLongMap|buildIntLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntSet(kotlin/Function1<androidx.collection/MutableIntSet, kotlin/Unit>): androidx.collection/IntSet // androidx.collection/buildIntSet|buildIntSet(kotlin.Function1<androidx.collection.MutableIntSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntSet, kotlin/Unit>): androidx.collection/IntSet // androidx.collection/buildIntSet|buildIntSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongFloatMap(kotlin/Function1<androidx.collection/MutableLongFloatMap, kotlin/Unit>): androidx.collection/LongFloatMap // androidx.collection/buildLongFloatMap|buildLongFloatMap(kotlin.Function1<androidx.collection.MutableLongFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongFloatMap, kotlin/Unit>): androidx.collection/LongFloatMap // androidx.collection/buildLongFloatMap|buildLongFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongIntMap(kotlin/Function1<androidx.collection/MutableLongIntMap, kotlin/Unit>): androidx.collection/LongIntMap // androidx.collection/buildLongIntMap|buildLongIntMap(kotlin.Function1<androidx.collection.MutableLongIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongIntMap, kotlin/Unit>): androidx.collection/LongIntMap // androidx.collection/buildLongIntMap|buildLongIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongList(kotlin/Function1<androidx.collection/MutableLongList, kotlin/Unit>): androidx.collection/LongList // androidx.collection/buildLongList|buildLongList(kotlin.Function1<androidx.collection.MutableLongList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongList(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongList, kotlin/Unit>): androidx.collection/LongList // androidx.collection/buildLongList|buildLongList(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongLongMap(kotlin/Function1<androidx.collection/MutableLongLongMap, kotlin/Unit>): androidx.collection/LongLongMap // androidx.collection/buildLongLongMap|buildLongLongMap(kotlin.Function1<androidx.collection.MutableLongLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongLongMap, kotlin/Unit>): androidx.collection/LongLongMap // androidx.collection/buildLongLongMap|buildLongLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongSet(kotlin/Function1<androidx.collection/MutableLongSet, kotlin/Unit>): androidx.collection/LongSet // androidx.collection/buildLongSet|buildLongSet(kotlin.Function1<androidx.collection.MutableLongSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongSet, kotlin/Unit>): androidx.collection/LongSet // androidx.collection/buildLongSet|buildLongSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongSet,kotlin.Unit>){}[0]
 final inline fun androidx.collection/isFull(kotlin/Long): kotlin/Boolean // androidx.collection/isFull|isFull(kotlin.Long){}[0]
 final inline fun androidx.collection/mutableDoubleListOf(): androidx.collection/MutableDoubleList // androidx.collection/mutableDoubleListOf|mutableDoubleListOf(){}[0]
 final inline fun androidx.collection/mutableDoubleListOf(kotlin/DoubleArray...): androidx.collection/MutableDoubleList // androidx.collection/mutableDoubleListOf|mutableDoubleListOf(kotlin.DoubleArray...){}[0]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
index 5b23890..b213ddc 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -947,3 +948,35 @@
 /** @return a new [MutableDoubleList] with the given elements, in order. */
 public inline fun mutableDoubleListOf(vararg elements: Double): MutableDoubleList =
     MutableDoubleList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [DoubleList] by populating a [MutableDoubleList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableDoubleList] can be populated.
+ */
+public inline fun buildDoubleList(
+    builderAction: MutableDoubleList.() -> Unit,
+): DoubleList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableDoubleList().apply(builderAction)
+}
+
+/**
+ * Builds a new [DoubleList] by populating a [MutableDoubleList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableDoubleList] can be populated.
+ */
+public inline fun buildDoubleList(
+    initialCapacity: Int,
+    builderAction: MutableDoubleList.() -> Unit,
+): DoubleList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableDoubleList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
index 072e286..9ef4cff 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [FloatFloatMap] by populating a [MutableFloatFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatFloatMap] can be populated.
+ */
+public inline fun buildFloatFloatMap(
+    builderAction: MutableFloatFloatMap.() -> Unit,
+): FloatFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatFloatMap] by populating a [MutableFloatFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatFloatMap] can be populated.
+ */
+public inline fun buildFloatFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatFloatMap.() -> Unit,
+): FloatFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatFloatMap] is a container with a [Map]-like interface for [Float] primitive keys and [Float]
  * primitive values.
  *
@@ -390,13 +428,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Float): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Float): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
index 1ff0eee..8d5f547 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [FloatIntMap] by populating a [MutableFloatIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatIntMap] can be populated.
+ */
+public inline fun buildFloatIntMap(
+    builderAction: MutableFloatIntMap.() -> Unit,
+): FloatIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatIntMap] by populating a [MutableFloatIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatIntMap] can be populated.
+ */
+public inline fun buildFloatIntMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatIntMap.() -> Unit,
+): FloatIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatIntMap] is a container with a [Map]-like interface for [Float] primitive keys and [Int]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Float): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Int): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index 3bf1ac3..a35ba55 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -940,3 +941,35 @@
 /** @return a new [MutableFloatList] with the given elements, in order. */
 public inline fun mutableFloatListOf(vararg elements: Float): MutableFloatList =
     MutableFloatList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [FloatList] by populating a [MutableFloatList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatList] can be populated.
+ */
+public inline fun buildFloatList(
+    builderAction: MutableFloatList.() -> Unit,
+): FloatList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatList().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatList] by populating a [MutableFloatList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatList] can be populated.
+ */
+public inline fun buildFloatList(
+    initialCapacity: Int,
+    builderAction: MutableFloatList.() -> Unit,
+): FloatList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
index a60cdc7..5685c12 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [FloatLongMap] by populating a [MutableFloatLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatLongMap] can be populated.
+ */
+public inline fun buildFloatLongMap(
+    builderAction: MutableFloatLongMap.() -> Unit,
+): FloatLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatLongMap] by populating a [MutableFloatLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatLongMap] can be populated.
+ */
+public inline fun buildFloatLongMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatLongMap.() -> Unit,
+): FloatLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatLongMap] is a container with a [Map]-like interface for [Float] primitive keys and [Long]
  * primitive values.
  *
@@ -390,13 +428,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Float): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Long): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
index bdb784d..4a93b9a 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [FloatObjectMap] by populating a [MutableFloatObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatObjectMap] can be populated.
+ */
+public inline fun <V> buildFloatObjectMap(
+    builderAction: MutableFloatObjectMap<V>.() -> Unit,
+): FloatObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatObjectMap] by populating a [MutableFloatObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatObjectMap] can be populated.
+ */
+public inline fun <V> buildFloatObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatObjectMap<V>.() -> Unit,
+): FloatObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatObjectMap] is a container with a [Map]-like interface for keys with [Float] primitives and
  * reference type values.
  *
@@ -383,13 +421,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Float): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: V): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
index 0279ff4..bc8473b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -100,6 +103,38 @@
     MutableFloatSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [FloatSet] by populating a [MutableFloatSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatSet] can be populated.
+ */
+public inline fun buildFloatSet(
+    builderAction: MutableFloatSet.() -> Unit,
+): FloatSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatSet] by populating a [MutableFloatSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatSet] can be populated.
+ */
+public inline fun buildFloatSet(
+    initialCapacity: Int,
+    builderAction: MutableFloatSet.() -> Unit,
+): FloatSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
index 10225ca..a1255ec 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntFloatMap] by populating a [MutableIntFloatMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntFloatMap] can be populated.
+ */
+public inline fun buildIntFloatMap(
+    builderAction: MutableIntFloatMap.() -> Unit,
+): IntFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntFloatMap] by populating a [MutableIntFloatMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntFloatMap] can be populated.
+ */
+public inline fun buildIntFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableIntFloatMap.() -> Unit,
+): IntFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntFloatMap] is a container with a [Map]-like interface for [Int] primitive keys and [Float]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Int): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Float): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
index 9f98b11..6b913ad 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntIntMap] by populating a [MutableIntIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntIntMap] can be populated.
+ */
+public inline fun buildIntIntMap(
+    builderAction: MutableIntIntMap.() -> Unit,
+): IntIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntIntMap] by populating a [MutableIntIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntIntMap] can be populated.
+ */
+public inline fun buildIntIntMap(
+    initialCapacity: Int,
+    builderAction: MutableIntIntMap.() -> Unit,
+): IntIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntIntMap] is a container with a [Map]-like interface for [Int] primitive keys and [Int]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Int): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Int): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index a4e6036..0edb357 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -932,3 +933,35 @@
 /** @return a new [MutableIntList] with the given elements, in order. */
 public inline fun mutableIntListOf(vararg elements: Int): MutableIntList =
     MutableIntList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [IntList] by populating a [MutableIntList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntList] can be populated.
+ */
+public inline fun buildIntList(
+    builderAction: MutableIntList.() -> Unit,
+): IntList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntList().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntList] by populating a [MutableIntList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntList] can be populated.
+ */
+public inline fun buildIntList(
+    initialCapacity: Int,
+    builderAction: MutableIntList.() -> Unit,
+): IntList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
index dedc3ae..87acb05 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntLongMap] by populating a [MutableIntLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntLongMap] can be populated.
+ */
+public inline fun buildIntLongMap(
+    builderAction: MutableIntLongMap.() -> Unit,
+): IntLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntLongMap] by populating a [MutableIntLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntLongMap] can be populated.
+ */
+public inline fun buildIntLongMap(
+    initialCapacity: Int,
+    builderAction: MutableIntLongMap.() -> Unit,
+): IntLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntLongMap] is a container with a [Map]-like interface for [Int] primitive keys and [Long]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Int): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Long): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
index ceab287..3e96e19 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [IntObjectMap] by populating a [MutableIntObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntObjectMap] can be populated.
+ */
+public inline fun <V> buildIntObjectMap(
+    builderAction: MutableIntObjectMap<V>.() -> Unit,
+): IntObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntObjectMap] by populating a [MutableIntObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntObjectMap] can be populated.
+ */
+public inline fun <V> buildIntObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableIntObjectMap<V>.() -> Unit,
+): IntObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntObjectMap] is a container with a [Map]-like interface for keys with [Int] primitives and
  * reference type values.
  *
@@ -383,13 +421,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Int): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: V): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
index 036f203..ac70e0e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -98,6 +101,38 @@
     MutableIntSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [IntSet] by populating a [MutableIntSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntSet] can be populated.
+ */
+public inline fun buildIntSet(
+    builderAction: MutableIntSet.() -> Unit,
+): IntSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntSet] by populating a [MutableIntSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntSet] can be populated.
+ */
+public inline fun buildIntSet(
+    initialCapacity: Int,
+    builderAction: MutableIntSet.() -> Unit,
+): IntSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
index bb34619..16d6b23 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [LongFloatMap] by populating a [MutableLongFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongFloatMap] can be populated.
+ */
+public inline fun buildLongFloatMap(
+    builderAction: MutableLongFloatMap.() -> Unit,
+): LongFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongFloatMap] by populating a [MutableLongFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongFloatMap] can be populated.
+ */
+public inline fun buildLongFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableLongFloatMap.() -> Unit,
+): LongFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongFloatMap] is a container with a [Map]-like interface for [Long] primitive keys and [Float]
  * primitive values.
  *
@@ -390,13 +428,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Long): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Float): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
index a9dc213..cefe009 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [LongIntMap] by populating a [MutableLongIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongIntMap] can be populated.
+ */
+public inline fun buildLongIntMap(
+    builderAction: MutableLongIntMap.() -> Unit,
+): LongIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongIntMap] by populating a [MutableLongIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongIntMap] can be populated.
+ */
+public inline fun buildLongIntMap(
+    initialCapacity: Int,
+    builderAction: MutableLongIntMap.() -> Unit,
+): LongIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongIntMap] is a container with a [Map]-like interface for [Long] primitive keys and [Int]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Long): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Int): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index 8f4a32e..927f1f0 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -936,3 +937,35 @@
 /** @return a new [MutableLongList] with the given elements, in order. */
 public inline fun mutableLongListOf(vararg elements: Long): MutableLongList =
     MutableLongList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [LongList] by populating a [MutableLongList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongList] can be populated.
+ */
+public inline fun buildLongList(
+    builderAction: MutableLongList.() -> Unit,
+): LongList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongList().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongList] by populating a [MutableLongList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongList] can be populated.
+ */
+public inline fun buildLongList(
+    initialCapacity: Int,
+    builderAction: MutableLongList.() -> Unit,
+): LongList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
index dbe6771..5ed07fa 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [LongLongMap] by populating a [MutableLongLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongLongMap] can be populated.
+ */
+public inline fun buildLongLongMap(
+    builderAction: MutableLongLongMap.() -> Unit,
+): LongLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongLongMap] by populating a [MutableLongLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongLongMap] can be populated.
+ */
+public inline fun buildLongLongMap(
+    initialCapacity: Int,
+    builderAction: MutableLongLongMap.() -> Unit,
+): LongLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongLongMap] is a container with a [Map]-like interface for [Long] primitive keys and [Long]
  * primitive values.
  *
@@ -390,13 +426,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Long): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Long): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
index eb674d9..b2168a9 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [LongObjectMap] by populating a [MutableLongObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongObjectMap] can be populated.
+ */
+public inline fun <V> buildLongObjectMap(
+    builderAction: MutableLongObjectMap<V>.() -> Unit,
+): LongObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongObjectMap] by populating a [MutableLongObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongObjectMap] can be populated.
+ */
+public inline fun <V> buildLongObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableLongObjectMap<V>.() -> Unit,
+): LongObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongObjectMap] is a container with a [Map]-like interface for keys with [Long] primitives and
  * reference type values.
  *
@@ -383,13 +421,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: Long): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: V): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
index 675d18e..09f75dc 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -99,6 +102,38 @@
     MutableLongSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [LongSet] by populating a [MutableLongSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongSet] can be populated.
+ */
+public inline fun buildLongSet(
+    builderAction: MutableLongSet.() -> Unit,
+): LongSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongSet] by populating a [MutableLongSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongSet] can be populated.
+ */
+public inline fun buildLongSet(
+    initialCapacity: Int,
+    builderAction: MutableLongSet.() -> Unit,
+): LongSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
index 6c8b64f..c555fed 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectFloatMap] by populating a [MutableObjectFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectFloatMap] can be populated.
+ */
+public inline fun <K> buildObjectFloatMap(
+    builderAction: MutableObjectFloatMap<K>.() -> Unit,
+): ObjectFloatMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectFloatMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectFloatMap] by populating a [MutableObjectFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectFloatMap] can be populated.
+ */
+public inline fun <K> buildObjectFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectFloatMap<K>.() -> Unit,
+): ObjectFloatMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectFloatMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectFloatMap] is a container with a [Map]-like interface for keys with reference types and
  * [Float] primitives for values.
  *
@@ -396,13 +434,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: K): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Float): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
index ec7ec4b..2dba86b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectIntMap] by populating a [MutableObjectIntMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectIntMap] can be populated.
+ */
+public inline fun <K> buildObjectIntMap(
+    builderAction: MutableObjectIntMap<K>.() -> Unit,
+): ObjectIntMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectIntMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectIntMap] by populating a [MutableObjectIntMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectIntMap] can be populated.
+ */
+public inline fun <K> buildObjectIntMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectIntMap<K>.() -> Unit,
+): ObjectIntMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectIntMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectIntMap] is a container with a [Map]-like interface for keys with reference types and [Int]
  * primitives for values.
  *
@@ -396,13 +434,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: K): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Int): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
index 3622689..9077f59 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectLongMap] by populating a [MutableObjectLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectLongMap] can be populated.
+ */
+public inline fun <K> buildObjectLongMap(
+    builderAction: MutableObjectLongMap<K>.() -> Unit,
+): ObjectLongMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectLongMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectLongMap] by populating a [MutableObjectLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectLongMap] can be populated.
+ */
+public inline fun <K> buildObjectLongMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectLongMap<K>.() -> Unit,
+): ObjectLongMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectLongMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectLongMap] is a container with a [Map]-like interface for keys with reference types and
  * [Long] primitives for values.
  *
@@ -396,13 +434,13 @@
         return count
     }
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
-    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
+    public inline operator fun contains(key: K): Boolean = containsKey(key)
 
-    /** Returns true if the specified [key] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [key] is present in this map, false otherwise. */
     public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
 
-    /** Returns true if the specified [value] is present in this hash map, false otherwise. */
+    /** Returns true if the specified [value] is present in this map, false otherwise. */
     public fun containsValue(value: Long): Boolean {
         forEachValue { v -> if (value == v) return true }
         return false
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
index 4c324c7..e3bde2e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildDoubleListFunction() {
+        val contract: Boolean
+        val l = buildDoubleList {
+            contract = true
+            add(2.0)
+            add(10.0)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2.0, l[0])
+        assertEquals(10.0, l[1])
+    }
+
+    @Test
+    fun buildDoubleListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildDoubleList(20) {
+                contract = true
+                add(2.0)
+                add(10.0)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2.0, l[0])
+        assertEquals(10.0, l[1])
+    }
+
+    @Test
     fun binarySearchDoubleList() {
         val l = mutableDoubleListOf(-2.0, -1.0, 2.0, 10.0, 10.0)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
index c6b52ef..f8068cc 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatFloatMapFunction() {
+        val contract: Boolean
+        val map = buildFloatFloatMap {
+            contract = true
+            put(1f, 1f)
+            put(2f, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1f])
+        assertEquals(2f, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatFloatMap(20) {
+                contract = true
+                put(1f, 1f)
+                put(2f, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1f])
+        assertEquals(2f, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableFloatFloatMap()
         map[1f] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
index 4d99c61..0e116a2 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatIntMapFunction() {
+        val contract: Boolean
+        val map = buildFloatIntMap {
+            contract = true
+            put(1f, 1)
+            put(2f, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1f])
+        assertEquals(2, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatIntMap(20) {
+                contract = true
+                put(1f, 1)
+                put(2f, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1f])
+        assertEquals(2, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableFloatIntMap()
         map[1f] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
index e1b4649c..17b2a9d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildFloatListFunction() {
+        val contract: Boolean
+        val l = buildFloatList {
+            contract = true
+            add(2f)
+            add(10f)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+    }
+
+    @Test
+    fun buildFloatListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildFloatList(20) {
+                contract = true
+                add(2f)
+                add(10f)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+    }
+
+    @Test
     fun binarySearchFloatList() {
         val l = mutableFloatListOf(-2f, -1f, 2f, 10f, 10f)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
index 6029c51..a307716 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatLongMapFunction() {
+        val contract: Boolean
+        val map = buildFloatLongMap {
+            contract = true
+            put(1f, 1L)
+            put(2f, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1f])
+        assertEquals(2L, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatLongMap(20) {
+                contract = true
+                put(1f, 1L)
+                put(2f, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1f])
+        assertEquals(2L, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableFloatLongMap()
         map[1f] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
index 572dd68..f0e20dc 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatObjectMapFunction() {
+        val contract: Boolean
+        val map = buildFloatObjectMap {
+            contract = true
+            put(1f, "World")
+            put(2f, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1f])
+        assertEquals("Monde", map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatObjectMap(20) {
+                contract = true
+                put(1f, "World")
+                put(2f, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1f])
+        assertEquals("Monde", map[2f])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableFloatObjectMap<String>()
         map[1f] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
index e5a105e..70d38a5 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildFloatSetFunction() {
+        val contract: Boolean
+        val set = buildFloatSet {
+            contract = true
+            add(1f)
+            add(2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+    }
+
+    @Test
+    fun buildFloatSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildFloatSet(20) {
+                contract = true
+                add(1f)
+                add(2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableFloatSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
index cf9a934..eb3ea90 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntFloatMapFunction() {
+        val contract: Boolean
+        val map = buildIntFloatMap {
+            contract = true
+            put(1, 1f)
+            put(2, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1])
+        assertEquals(2f, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntFloatMap(20) {
+                contract = true
+                put(1, 1f)
+                put(2, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1])
+        assertEquals(2f, map[2])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableIntFloatMap()
         map[1] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
index b7d318b..4a2691d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntIntMapFunction() {
+        val contract: Boolean
+        val map = buildIntIntMap {
+            contract = true
+            put(1, 1)
+            put(2, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1])
+        assertEquals(2, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntIntMap(20) {
+                contract = true
+                put(1, 1)
+                put(2, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1])
+        assertEquals(2, map[2])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableIntIntMap()
         map[1] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
index cdc0cf9..6023ec2 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildIntListFunction() {
+        val contract: Boolean
+        val l = buildIntList {
+            contract = true
+            add(2)
+            add(10)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+    }
+
+    @Test
+    fun buildIntListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildIntList(20) {
+                contract = true
+                add(2)
+                add(10)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+    }
+
+    @Test
     fun binarySearchIntList() {
         val l = mutableIntListOf(-2, -1, 2, 10, 10)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
index e115dc6..7256aa0 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntLongMapFunction() {
+        val contract: Boolean
+        val map = buildIntLongMap {
+            contract = true
+            put(1, 1L)
+            put(2, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1])
+        assertEquals(2L, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntLongMap(20) {
+                contract = true
+                put(1, 1L)
+                put(2, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1])
+        assertEquals(2L, map[2])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableIntLongMap()
         map[1] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
index 9d002e6..2193d75 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntObjectMapFunction() {
+        val contract: Boolean
+        val map = buildIntObjectMap {
+            contract = true
+            put(1, "World")
+            put(2, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1])
+        assertEquals("Monde", map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntObjectMap(20) {
+                contract = true
+                put(1, "World")
+                put(2, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1])
+        assertEquals("Monde", map[2])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableIntObjectMap<String>()
         map[1] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
index 66c30d3..46a4c63 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildIntSetFunction() {
+        val contract: Boolean
+        val set = buildIntSet {
+            contract = true
+            add(1)
+            add(2)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+    }
+
+    @Test
+    fun buildIntSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildIntSet(20) {
+                contract = true
+                add(1)
+                add(2)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableIntSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
index 507ddf0..4201211 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongFloatMapFunction() {
+        val contract: Boolean
+        val map = buildLongFloatMap {
+            contract = true
+            put(1L, 1f)
+            put(2L, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1L])
+        assertEquals(2f, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongFloatMap(20) {
+                contract = true
+                put(1L, 1f)
+                put(2L, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1L])
+        assertEquals(2f, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableLongFloatMap()
         map[1L] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
index 75488a2..568fc7a 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongIntMapFunction() {
+        val contract: Boolean
+        val map = buildLongIntMap {
+            contract = true
+            put(1L, 1)
+            put(2L, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1L])
+        assertEquals(2, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongIntMap(20) {
+                contract = true
+                put(1L, 1)
+                put(2L, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1L])
+        assertEquals(2, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableLongIntMap()
         map[1L] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
index af0536e..64019c1 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildLongListFunction() {
+        val contract: Boolean
+        val l = buildLongList {
+            contract = true
+            add(2L)
+            add(10L)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+    }
+
+    @Test
+    fun buildLongListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildLongList(20) {
+                contract = true
+                add(2L)
+                add(10L)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+    }
+
+    @Test
     fun binarySearchLongList() {
         val l = mutableLongListOf(-2L, -1L, 2L, 10L, 10L)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
index a22631b..6af2158 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongLongMapFunction() {
+        val contract: Boolean
+        val map = buildLongLongMap {
+            contract = true
+            put(1L, 1L)
+            put(2L, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1L])
+        assertEquals(2L, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongLongMap(20) {
+                contract = true
+                put(1L, 1L)
+                put(2L, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1L])
+        assertEquals(2L, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableLongLongMap()
         map[1L] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
index f8be517..3b6747d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongObjectMapFunction() {
+        val contract: Boolean
+        val map = buildLongObjectMap {
+            contract = true
+            put(1L, "World")
+            put(2L, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1L])
+        assertEquals("Monde", map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongObjectMap(20) {
+                contract = true
+                put(1L, "World")
+                put(2L, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1L])
+        assertEquals("Monde", map[2L])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableLongObjectMap<String>()
         map[1L] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
index dc5b7e7..577e722e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildLongSetFunction() {
+        val contract: Boolean
+        val set = buildLongSet {
+            contract = true
+            add(1L)
+            add(2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+    }
+
+    @Test
+    fun buildLongSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildLongSet(20) {
+                contract = true
+                add(1L)
+                add(2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableLongSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
index 90244ab..19c1cc6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectFloatMapFunction() {
+        val contract: Boolean
+        val map = buildObjectFloatMap {
+            contract = true
+            put("Hello", 1f)
+            put("Bonjour", 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map["Hello"])
+        assertEquals(2f, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectFloatMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectFloatMap(20) {
+                contract = true
+                put("Hello", 1f)
+                put("Bonjour", 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map["Hello"])
+        assertEquals(2f, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectFloatMap<String>()
         map["Hello"] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
index bc640eb..62b7a2c 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectIntMapFunction() {
+        val contract: Boolean
+        val map = buildObjectIntMap {
+            contract = true
+            put("Hello", 1)
+            put("Bonjour", 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map["Hello"])
+        assertEquals(2, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectIntMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectIntMap(20) {
+                contract = true
+                put("Hello", 1)
+                put("Bonjour", 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map["Hello"])
+        assertEquals(2, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectIntMap<String>()
         map["Hello"] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
index f728b95..6eba1a6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectLongMapFunction() {
+        val contract: Boolean
+        val map = buildObjectLongMap {
+            contract = true
+            put("Hello", 1L)
+            put("Bonjour", 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map["Hello"])
+        assertEquals(2L, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectLongMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectLongMap(20) {
+                contract = true
+                put("Hello", 1L)
+                put("Bonjour", 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map["Hello"])
+        assertEquals(2L, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectLongMap<String>()
         map["Hello"] = 1L
diff --git a/collection/collection/template/ObjectPValueMap.kt.template b/collection/collection/template/ObjectPValueMap.kt.template
index bfc791c..878080f 100644
--- a/collection/collection/template/ObjectPValueMap.kt.template
+++ b/collection/collection/template/ObjectPValueMap.kt.template
@@ -18,12 +18,16 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -240,6 +244,40 @@
     }
 
 /**
+ * Builds a new [ObjectPValueMap] by populating a [MutableObjectPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectPValueMap] can be populated.
+ */
+public inline fun <K> buildObjectPValueMap(
+    builderAction: MutableObjectPValueMap<K>.() -> Unit,
+): ObjectPValueMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectPValueMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectPValueMap] by populating a [MutableObjectPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectPValueMap] can be populated.
+ */
+public inline fun <K> buildObjectPValueMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectPValueMap<K>.() -> Unit,
+): ObjectPValueMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectPValueMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectPValueMap] is a container with a [Map]-like interface for keys with
  * reference types and [PValue] primitives for values.
  *
@@ -470,19 +508,19 @@
     }
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
-    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+    public inline operator fun contains(key: K): Boolean = containsKey(key)
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
     public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
 
     /**
-     * Returns true if the specified [value] is present in this hash map, false
+     * Returns true if the specified [value] is present in this map, false
      * otherwise.
      */
     public fun containsValue(value: PValue): Boolean {
diff --git a/collection/collection/template/ObjectPValueMapTest.kt.template b/collection/collection/template/ObjectPValueMapTest.kt.template
index 988abb3..c84db2d 100644
--- a/collection/collection/template/ObjectPValueMapTest.kt.template
+++ b/collection/collection/template/ObjectPValueMapTest.kt.template
@@ -189,6 +189,35 @@
     }
 
     @Test
+    fun buildObjectPValueMapFunction() {
+        val contract: Boolean
+        val map = buildObjectPValueMap {
+            contract = true
+            put("Hello", 1ValueSuffix)
+            put("Bonjour", 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(2ValueSuffix, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectPValueMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildObjectPValueMap(20) {
+            contract = true
+            put("Hello", 1ValueSuffix)
+            put("Bonjour", 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(2ValueSuffix, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectPValueMap<String>()
         map["Hello"] = 1ValueSuffix
diff --git a/collection/collection/template/PKeyList.kt.template b/collection/collection/template/PKeyList.kt.template
index 03233b7..61a2ff2 100644
--- a/collection/collection/template/PKeyList.kt.template
+++ b/collection/collection/template/PKeyList.kt.template
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -1012,3 +1013,37 @@
  */
 public inline fun mutablePKeyListOf(vararg elements: PKey): MutablePKeyList =
     MutablePKeyList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [PKeyList] by populating a [MutablePKeyList] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyList] can be populated.
+ */
+public inline fun buildPKeyList(
+    builderAction: MutablePKeyList.() -> Unit,
+): PKeyList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyList().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyList] by populating a [MutablePKeyList] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyList] can be populated.
+ */
+public inline fun buildPKeyList(
+    initialCapacity: Int,
+    builderAction: MutablePKeyList.() -> Unit,
+): PKeyList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/template/PKeyListTest.kt.template b/collection/collection/template/PKeyListTest.kt.template
index 8f3d8e0..417c489 100644
--- a/collection/collection/template/PKeyListTest.kt.template
+++ b/collection/collection/template/PKeyListTest.kt.template
@@ -751,6 +751,35 @@
     }
 
     @Test
+    fun buildPKeyListFunction() {
+        val contract: Boolean
+        val l = buildPKeyList {
+            contract = true
+            add(2KeySuffix)
+            add(10KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+    }
+
+    @Test
+    fun buildPKeyListWithCapacityFunction() {
+        val contract: Boolean
+        val l = buildPKeyList(20) {
+            contract = true
+            add(2KeySuffix)
+            add(10KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+    }
+
+    @Test
     fun binarySearchPKeyList() {
         val l = mutablePKeyListOf(-2KeySuffix, -1KeySuffix, 2KeySuffix, 10KeySuffix, 10KeySuffix)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/template/PKeyObjectMap.kt.template b/collection/collection/template/PKeyObjectMap.kt.template
index 39bb237..aa140dd5 100644
--- a/collection/collection/template/PKeyObjectMap.kt.template
+++ b/collection/collection/template/PKeyObjectMap.kt.template
@@ -18,11 +18,15 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -227,6 +231,40 @@
     }
 
 /**
+ * Builds a new [PKeyObjectMap] by populating a [MutablePKeyObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyObjectMap] can be populated.
+ */
+public inline fun <V> buildPKeyObjectMap(
+    builderAction: MutablePKeyObjectMap<V>.() -> Unit,
+): PKeyObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyObjectMap] by populating a [MutablePKeyObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyObjectMap] can be populated.
+ */
+public inline fun <V> buildPKeyObjectMap(
+    initialCapacity: Int,
+    builderAction: MutablePKeyObjectMap<V>.() -> Unit,
+): PKeyObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeyObjectMap] is a container with a [Map]-like interface for keys with
  * [PKey] primitives and reference type values.
  *
@@ -451,19 +489,19 @@
     }
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
-    public operator fun contains(key: PKey): Boolean = findKeyIndex(key) >= 0
+    public inline operator fun contains(key: PKey): Boolean = containsKey(key)
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
     public fun containsKey(key: PKey): Boolean = findKeyIndex(key) >= 0
 
     /**
-     * Returns true if the specified [value] is present in this hash map, false
+     * Returns true if the specified [value] is present in this map, false
      * otherwise.
      */
     public fun containsValue(value: V): Boolean {
diff --git a/collection/collection/template/PKeyObjectMapTest.kt.template b/collection/collection/template/PKeyObjectMapTest.kt.template
index ea5cfb8..de9c4ff 100644
--- a/collection/collection/template/PKeyObjectMapTest.kt.template
+++ b/collection/collection/template/PKeyObjectMapTest.kt.template
@@ -188,6 +188,35 @@
     }
 
     @Test
+    fun buildPKeyObjectMapFunction() {
+        val contract: Boolean
+        val map = buildPKeyObjectMap {
+            contract = true
+            put(1KeySuffix, "World")
+            put(2KeySuffix, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1KeySuffix])
+        assertEquals("Monde", map[2KeySuffix])
+    }
+
+    @Test
+    fun buildPKeyObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildPKeyObjectMap(20) {
+            contract = true
+            put(1KeySuffix, "World")
+            put(2KeySuffix, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1KeySuffix])
+        assertEquals("Monde", map[2KeySuffix])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutablePKeyObjectMap<String>()
         map[1KeySuffix] = "World"
diff --git a/collection/collection/template/PKeyPValueMap.kt.template b/collection/collection/template/PKeyPValueMap.kt.template
index 524dbe7..61ac9d8 100644
--- a/collection/collection/template/PKeyPValueMap.kt.template
+++ b/collection/collection/template/PKeyPValueMap.kt.template
@@ -18,11 +18,15 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -225,6 +229,40 @@
     }
 
 /**
+ * Builds a new [PKeyPValueMap] by populating a [MutablePKeyPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyPValueMap] can be populated.
+ */
+public inline fun buildPKeyPValueMap(
+    builderAction: MutablePKeyPValueMap.() -> Unit,
+): PKeyPValueMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyPValueMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyPValueMap] by populating a [MutablePKeyPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyPValueMap] can be populated.
+ */
+public inline fun buildPKeyPValueMap(
+    initialCapacity: Int,
+    builderAction: MutablePKeyPValueMap.() -> Unit,
+): PKeyPValueMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyPValueMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeyPValueMap] is a container with a [Map]-like interface for
  * [PKey] primitive keys and [PValue] primitive values.
  *
@@ -453,19 +491,19 @@
     }
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
-    public operator fun contains(key: PKey): Boolean = findKeyIndex(key) >= 0
+    public inline operator fun contains(key: PKey): Boolean = containsKey(key)
 
     /**
-     * Returns true if the specified [key] is present in this hash map, false
+     * Returns true if the specified [key] is present in this map, false
      * otherwise.
      */
     public fun containsKey(key: PKey): Boolean = findKeyIndex(key) >= 0
 
     /**
-     * Returns true if the specified [value] is present in this hash map, false
+     * Returns true if the specified [value] is present in this map, false
      * otherwise.
      */
     public fun containsValue(value: PValue): Boolean {
diff --git a/collection/collection/template/PKeyPValueMapTest.kt.template b/collection/collection/template/PKeyPValueMapTest.kt.template
index a61070f..a190dae 100644
--- a/collection/collection/template/PKeyPValueMapTest.kt.template
+++ b/collection/collection/template/PKeyPValueMapTest.kt.template
@@ -188,6 +188,35 @@
     }
 
     @Test
+    fun buildPKeyPValueMapFunction() {
+        val contract: Boolean
+        val map = buildPKeyPValueMap {
+            contract = true
+            put(1KeySuffix, 1ValueSuffix)
+            put(2KeySuffix, 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+    }
+
+    @Test
+    fun buildPKeyObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildPKeyPValueMap(20) {
+            contract = true
+            put(1KeySuffix, 1ValueSuffix)
+            put(2KeySuffix, 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutablePKeyPValueMap()
         map[1KeySuffix] = 1ValueSuffix
diff --git a/collection/collection/template/PKeySet.kt.template b/collection/collection/template/PKeySet.kt.template
index 38e36a4..a33c487 100644
--- a/collection/collection/template/PKeySet.kt.template
+++ b/collection/collection/template/PKeySet.kt.template
@@ -23,12 +23,15 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -126,6 +129,40 @@
     MutablePKeySet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [PKeySet] by populating a [MutablePKeySet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeySet] can be populated.
+ */
+public inline fun buildPKeySet(
+    builderAction: MutablePKeySet.() -> Unit,
+): PKeySet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeySet().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeySet] by populating a [MutablePKeySet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeySet] can be populated.
+ */
+public inline fun buildPKeySet(
+    initialCapacity: Int,
+    builderAction: MutablePKeySet.() -> Unit,
+): PKeySet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeySet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeySet] is a container with a [Set]-like interface designed to avoid
  * allocations, including boxing.
  *
diff --git a/collection/collection/template/PKeySetTest.kt.template b/collection/collection/template/PKeySetTest.kt.template
index f9b12d5..05813d0 100644
--- a/collection/collection/template/PKeySetTest.kt.template
+++ b/collection/collection/template/PKeySetTest.kt.template
@@ -562,6 +562,35 @@
     }
 
     @Test
+    fun buildPKeySetFunction() {
+        val contract: Boolean
+        val set = buildPKeySet {
+            contract = true
+            add(1KeySuffix)
+            add(2KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun buildPKeySetWithCapacityFunction() {
+        val contract: Boolean
+        val set = buildPKeySet(20) {
+            contract = true
+            add(1KeySuffix)
+            add(2KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutablePKeySetOf()
 
diff --git a/collection/collection/template/ValueClassList.kt.template b/collection/collection/template/ValueClassList.kt.template
index 4ce50e6..88fc2dc 100644
--- a/collection/collection/template/ValueClassList.kt.template
+++ b/collection/collection/template/ValueClassList.kt.template
@@ -24,6 +24,7 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package PACKAGE
 
@@ -34,6 +35,7 @@
 import androidx.collection.mutablePRIMITIVEListOf
 import VALUE_PKG.VALUE_CLASS
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -57,7 +59,6 @@
  * the appropriate synchronization. It is also not safe to mutate during reentrancy --
  * in the middle of a [forEach], for example. However, concurrent reads are safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class VALUE_CLASSList(val list: PRIMITIVEList) {
     /**
@@ -377,7 +378,6 @@
  *
  * @constructor Creates a [MutableVALUE_CLASSList] with a [capacity] of `initialCapacity`.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class MutableVALUE_CLASSList(val list: MutablePRIMITIVEList) {
     public constructor(initialCapacity: Int = 16) : this(MutablePRIMITIVEList(initialCapacity))
@@ -934,3 +934,37 @@
         element3.BACKING_PROPERTY
     )
 )
+
+/**
+ * Builds a new [VALUE_CLASSList] by populating a [MutableVALUE_CLASSList] using the given
+ * [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableVALUE_CLASSList] can be populated.
+ */
+VISIBILITY inline fun buildVALUE_CLASSList(
+    builderAction: MutableVALUE_CLASSList.() -> Unit,
+): VALUE_CLASSList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSList().apply(builderAction).asVALUE_CLASSList()
+}
+
+/**
+ * Builds a new [VALUE_CLASSList] by populating a [MutableVALUE_CLASSList] using the given
+ * [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableVALUE_CLASSList] can be populated.
+ */
+VISIBILITY inline fun buildVALUE_CLASSList(
+    initialCapacity: Int,
+    builderAction: MutableVALUE_CLASSList.() -> Unit,
+): VALUE_CLASSList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSList(initialCapacity).apply(builderAction).asVALUE_CLASSList()
+}
diff --git a/collection/collection/template/ValueClassSet.kt.template b/collection/collection/template/ValueClassSet.kt.template
index de43cdf..cd2f51f 100644
--- a/collection/collection/template/ValueClassSet.kt.template
+++ b/collection/collection/template/ValueClassSet.kt.template
@@ -24,6 +24,7 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package PACKAGE
 
@@ -34,6 +35,7 @@
 import androidx.collection.mutablePRIMITIVESetOf
 import VALUE_PKG.VALUE_CLASS
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -137,6 +139,37 @@
     )
 
 /**
+ * Builds a new [VALUE_CLASSSet] by populating a [MutableVALUE_CLASSSet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ */
+VISIBILITY inline fun buildVALUE_CLASSSet(
+    builderAction: MutableVALUE_CLASSSet.() -> Unit,
+): VALUE_CLASSSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSSet().apply(builderAction).asVALUE_CLASSSet()
+}
+
+/**
+ * Builds a new [VALUE_CLASSSet] by populating a [MutableVALUE_CLASSSet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ */
+VISIBILITY inline fun buildVALUE_CLASSSet(
+    initialCapacity: Int,
+    builderAction: MutableVALUE_CLASSSet.() -> Unit,
+): VALUE_CLASSSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSSet(initialCapacity).apply(builderAction).asVALUE_CLASSSet()
+}
+
+/**
  * [VALUE_CLASSSet] is a container with a [Set]-like interface designed to avoid
  * allocations, including boxing.
  *
@@ -151,7 +184,6 @@
  *
  * @see [MutableVALUE_CLASSSet]
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class VALUE_CLASSSet(val set: PRIMITIVESet) {
     /**
@@ -302,7 +334,6 @@
  * the set (insertion or removal for instance), the calling code must provide
  * the appropriate synchronization. Concurrent reads are however safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class MutableVALUE_CLASSSet(val set: MutablePRIMITIVESet) {
     /**
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 1bb80e5..5611700 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.animation
 
+import android.annotation.SuppressLint
 import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.MutableTransitionState
@@ -28,6 +29,7 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -63,6 +65,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -93,7 +96,6 @@
     @get:Rule
     val ruleChain: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()).around(rule)
 
-    @OptIn(InternalAnimationApi::class)
     @Test
     fun AnimatedContentSizeTransformTest() {
         val size1 = 40
@@ -990,6 +992,76 @@
         rule.waitForIdle()
     }
 
+    @OptIn(ExperimentalSharedTransitionApi::class)
+    @SuppressLint("UnusedContentLambdaTargetStateParameter")
+    @Test
+    fun testSizeTransformAlwaysContinuous() {
+        var large by mutableStateOf(false)
+        var currentWidth: Int = 0
+        var currentHeight: Int = 0
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                LookaheadScope {
+                    Box(Modifier.clickable { large = !large }) {
+                        AnimatedContent(
+                            label = "Test",
+                            modifier =
+                                Modifier.animateBounds(
+                                        this@LookaheadScope,
+                                        if (!large) Modifier.size(200.dp) else Modifier.size(300.dp)
+                                    )
+                                    .layout { m, c ->
+                                        m.measure(
+                                                c.copy(
+                                                    maxWidth = Constraints.Infinity,
+                                                    maxHeight = Constraints.Infinity
+                                                )
+                                            )
+                                            .run {
+                                                if (!isLookingAhead) {
+                                                    currentWidth = width
+                                                    currentHeight = height
+                                                }
+                                                layout(width, height) { place(0, 0) }
+                                            }
+                                    }
+                                    .background(Color.Gray),
+                            targetState = large,
+                            contentKey = { true },
+                            transitionSpec = { fadeIn().togetherWith(fadeOut()) }
+                        ) {
+                            Box(Modifier.background(Color.Black).size(200.dp, 100.dp))
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        large = true
+
+        assertEquals(200, currentWidth)
+        assertEquals(200, currentHeight)
+        // Expect size to grow
+        var lastWidth: Int = 200
+        var lastHeight: Int = 200
+
+        fun doFrame() {
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+            assertTrue(currentWidth >= lastWidth)
+            assertTrue(currentHeight >= lastHeight)
+            lastWidth = currentWidth
+            lastHeight = currentHeight
+        }
+
+        repeat(5) { doFrame() }
+
+        while (currentWidth != 300 || currentHeight != 300) {
+            doFrame()
+        }
+    }
+
     @Test
     fun testTargetChangeLookaheadPlacement() {
         var lookaheadPosition1: Offset? = null
@@ -1087,7 +1159,6 @@
         assertEquals(expected.y, actual.y, 0.00001f)
     }
 
-    @OptIn(InternalAnimationApi::class)
     private val Transition<*>.playTimeMillis
         get() = (playTimeNanos / 1_000_000L).toInt()
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index f095141..a8d32ea 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -60,6 +60,8 @@
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -89,8 +91,8 @@
  * If [targetState] is expected to mutate frequently and not all mutations should be treated as
  * target state change, consider defining a mapping between [targetState] and a key in [contentKey].
  * As a result, transitions will be triggered when the resulting key changes. In other words, there
- * will be no animation when switching between [targetState]s that share the same same key. By
- * default, the key will be the same as the targetState object.
+ * will be no animation when switching between [targetState]s that share the same key. By default,
+ * the key will be the same as the targetState object.
  *
  * By default, the [ContentTransform] will be a delayed [fadeIn] of the target content and a delayed
  * [scaleIn] [togetherWith] a [fadeOut] of the initial content, using a [SizeTransform] to animate
@@ -563,16 +565,23 @@
                 shouldAnimateSize = true
             }
         }
+        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?
         return if (shouldAnimateSize) {
-            val sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConverter)
-            remember(sizeAnimation) {
-                (if (sizeTransform.value?.clip == false) Modifier else Modifier.clipToBounds())
-                    .then(SizeModifier(sizeAnimation, sizeTransform))
+                sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConverter)
+                remember(sizeAnimation) {
+                    (if (sizeTransform.value?.clip == false) Modifier else Modifier.clipToBounds())
+                }
+            } else {
+                sizeAnimation = null
+                animatedSize = null
+                Modifier
             }
-        } else {
-            animatedSize = null
-            Modifier
-        }
+            .then(
+                // Keep the SizeModifier in the chain and switch between active animating and
+                // passive
+                // observing based on sizeAnimation's value
+                SizeModifierElement(sizeAnimation, sizeTransform)
+            )
     }
 
     // This helps track the target measurable without affecting the placement order. Target
@@ -587,31 +596,88 @@
         }
     }
 
-    private inner class SizeModifier(
-        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
-        val sizeTransform: State<SizeTransform?>,
-    ) : LayoutModifierWithPassThroughIntrinsics() {
+    private inner class SizeModifierElement(
+        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?,
+        val sizeTransform: State<SizeTransform?>
+    ) : ModifierNodeElement<SizeModifierNode>() {
+        override fun create(): SizeModifierNode {
+            return SizeModifierNode(sizeAnimation, sizeTransform)
+        }
+
+        override fun hashCode(): Int {
+            return sizeAnimation.hashCode() * 31 + sizeTransform.hashCode()
+        }
+
+        override fun equals(other: Any?): Boolean {
+            return other is AnimatedContentTransitionScopeImpl<*>.SizeModifierElement &&
+                other.sizeAnimation == sizeAnimation &&
+                other.sizeTransform == sizeTransform
+        }
+
+        override fun update(node: SizeModifierNode) {
+            node.sizeAnimation = sizeAnimation
+            node.sizeTransform = sizeTransform
+        }
+
+        override fun InspectorInfo.inspectableProperties() {
+            name = "sizeTransform"
+            properties["sizeAnimation"] = sizeAnimation
+            properties["sizeTransform"] = sizeTransform
+        }
+    }
+
+    private inner class SizeModifierNode(
+        var sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?,
+        var sizeTransform: State<SizeTransform?>,
+    ) : LayoutModifierNodeWithPassThroughIntrinsics() {
+        // This is used to track the on-going size change so that when the target state changes,
+        // we always start from the last seen size to the new target size to ensure continuity.
+        private var lastSize: IntSize = UnspecifiedSize
+
+        private fun lastContinuousSizeOrDefault(default: IntSize) =
+            if (lastSize == UnspecifiedSize) default else lastSize
+
         override fun MeasureScope.measure(
             measurable: Measurable,
             constraints: Constraints
         ): MeasureResult {
             val placeable = measurable.measure(constraints)
-            val size =
-                sizeAnimation.animate(
-                    transitionSpec = {
-                        val initial = targetSizeMap[initialState]?.value ?: IntSize.Zero
-                        val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
-                        sizeTransform.value?.createAnimationSpec(initial, target) ?: spring()
-                    }
-                ) {
-                    targetSizeMap[it]?.value ?: IntSize.Zero
-                }
-            animatedSize = size
             val measuredSize: IntSize
             if (isLookingAhead) {
                 measuredSize = IntSize(placeable.width, placeable.height)
+            } else if (sizeAnimation == null) {
+                // Observing mode
+                measuredSize = IntSize(placeable.width, placeable.height)
+                lastSize = IntSize(placeable.width, placeable.height)
             } else {
+                val currentSize = IntSize(placeable.width, placeable.height)
+                val size =
+                    sizeAnimation!!.animate(
+                        transitionSpec = {
+                            val initial =
+                                if (
+                                    initialState ==
+                                        [email protected]
+                                ) {
+                                    lastContinuousSizeOrDefault(currentSize)
+                                } else {
+                                    targetSizeMap[initialState]?.value ?: IntSize.Zero
+                                }
+                            val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
+                            sizeTransform.value?.createAnimationSpec(initial, target)
+                                ?: spring(stiffness = Spring.StiffnessMediumLow)
+                        }
+                    ) {
+                        // Animate from the approach size to the lookahead size.
+                        if (it == initialState) {
+                            lastContinuousSizeOrDefault(currentSize)
+                        } else {
+                            targetSizeMap[it]?.value ?: IntSize.Zero
+                        }
+                    }
+                animatedSize = size
                 measuredSize = size.value
+                lastSize = size.value
             }
             return layout(measuredSize.width, measuredSize.height) {
                 val offset =
@@ -626,6 +692,8 @@
     }
 }
 
+private val UnspecifiedSize: IntSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
+
 /**
  * Receiver scope for content lambda for AnimatedContent. In this scope,
  * [transition][AnimatedVisibilityScope.transition] can be used to observe the state of the
@@ -657,7 +725,7 @@
  * treated as target state change, consider defining a mapping between [Transition.targetState] and
  * a key in [contentKey]. As a result, transitions will be triggered when the resulting key changes.
  * In other words, there will be no animation when switching between [Transition.targetState]s that
- * share the same same key. By default, the key will be the same as the targetState object.
+ * share the same key. By default, the key will be the same as the targetState object.
  *
  * By default, the [ContentTransform] will be a delayed [fadeIn] of the target content and a delayed
  * [scaleIn] [togetherWith] a [fadeOut] of the initial content, using a [SizeTransform] to animate
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
index 4c79c36..1b2fec6 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
@@ -54,8 +54,8 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastMaxBy
-import androidx.compose.ui.util.fastMaxOfOrNull
+import androidx.compose.ui.util.fastMaxOfOrDefault
+import kotlin.math.max
 
 /**
  * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
@@ -781,9 +781,15 @@
         measurables: List<Measurable>,
         constraints: Constraints
     ): MeasureResult {
-        val placeables = measurables.fastMap { it.measure(constraints) }
-        val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
-        val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
+        var maxWidth = 0
+        var maxHeight = 0
+        val placeables =
+            measurables.fastMap {
+                it.measure(constraints).apply {
+                    maxWidth = max(maxWidth, width)
+                    maxHeight = max(maxHeight, height)
+                }
+            }
         // Position the children.
         if (isLookingAhead) {
             hasLookaheadOccurred = true
@@ -798,22 +804,22 @@
     override fun IntrinsicMeasureScope.minIntrinsicWidth(
         measurables: List<IntrinsicMeasurable>,
         height: Int
-    ) = measurables.fastMaxOfOrNull { it.minIntrinsicWidth(height) } ?: 0
+    ) = measurables.fastMaxOfOrDefault(0) { it.minIntrinsicWidth(height) }
 
     override fun IntrinsicMeasureScope.minIntrinsicHeight(
         measurables: List<IntrinsicMeasurable>,
         width: Int
-    ) = measurables.fastMaxOfOrNull { it.minIntrinsicHeight(width) } ?: 0
+    ) = measurables.fastMaxOfOrDefault(0) { it.minIntrinsicHeight(width) }
 
     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
         measurables: List<IntrinsicMeasurable>,
         height: Int
-    ) = measurables.fastMaxOfOrNull { it.maxIntrinsicWidth(height) } ?: 0
+    ) = measurables.fastMaxOfOrDefault(0) { it.maxIntrinsicWidth(height) }
 
     override fun IntrinsicMeasureScope.maxIntrinsicHeight(
         measurables: List<IntrinsicMeasurable>,
         width: Int
-    ) = measurables.fastMaxOfOrNull { it.maxIntrinsicHeight(width) } ?: 0
+    ) = measurables.fastMaxOfOrDefault(0) { it.maxIntrinsicHeight(width) }
 }
 
 // This converts Boolean visible to EnterExitState
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
index 57d5833..8102e2c 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
@@ -266,25 +266,3 @@
         width: Int
     ) = measurable.maxIntrinsicHeight(width)
 }
-
-internal abstract class LayoutModifierWithPassThroughIntrinsics : LayoutModifier {
-    final override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.minIntrinsicWidth(height)
-
-    final override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.minIntrinsicHeight(width)
-
-    final override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.maxIntrinsicWidth(height)
-
-    final override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.maxIntrinsicHeight(width)
-}
diff --git a/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/NonLambdaOffsetModifierDetector.kt b/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/NonLambdaOffsetModifierDetector.kt
index 52974ba..f481919 100644
--- a/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/NonLambdaOffsetModifierDetector.kt
+++ b/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/NonLambdaOffsetModifierDetector.kt
@@ -33,7 +33,7 @@
 import com.android.tools.lint.detector.api.UastLintUtils.Companion.tryResolveUDeclaration
 import com.intellij.psi.PsiMethod
 import java.util.EnumSet
-import kotlinx.metadata.KmClassifier
+import kotlin.metadata.KmClassifier
 import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UDeclaration
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
index 85264af..aa9c110 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
@@ -24,15 +24,18 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.compose.foundation.demos.collection
 
+import androidx.annotation.IntRange
 import androidx.collection.LongList
 import androidx.collection.MutableLongList
 import androidx.collection.emptyLongList
 import androidx.collection.mutableLongListOf
 import androidx.compose.ui.graphics.Color
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -55,21 +58,20 @@
  * calling code must provide the appropriate synchronization. It is also not safe to mutate during
  * reentrancy -- in the middle of a [forEach], for example. However, concurrent reads are safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class ColorList(val list: LongList) {
     /** The number of elements in the [ColorList]. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = list.size
 
     /** Returns the last valid index in the [ColorList]. This can be `-1` when the list is empty. */
-    @get:androidx.annotation.IntRange(from = -1)
+    @get:IntRange(from = -1)
     public inline val lastIndex: Int
         get() = list.lastIndex
 
     /** Returns an [IntRange] of the valid indices for this [ColorList]. */
-    public inline val indices: IntRange
+    public inline val indices: kotlin.ranges.IntRange
         get() = list.indices
 
     /** Returns `true` if the collection has no elements in it. */
@@ -244,14 +246,14 @@
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline operator fun get(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun elementAt(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
@@ -263,7 +265,7 @@
      *   index not in the list.
      */
     public inline fun elementAtOrElse(
-        @androidx.annotation.IntRange(from = 0) index: Int,
+        @IntRange(from = 0) index: Int,
         defaultValue: (index: Int) -> Color
     ): Color = Color(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
 
@@ -350,23 +352,22 @@
  *
  * @constructor Creates a [MutableColorList] with a [capacity] of `initialCapacity`.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class MutableColorList(val list: MutableLongList) {
     public constructor(initialCapacity: Int = 16) : this(MutableLongList(initialCapacity))
 
     /** The number of elements in the [ColorList]. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = list.size
 
     /** Returns the last valid index in the [ColorList]. This can be `-1` when the list is empty. */
-    @get:androidx.annotation.IntRange(from = -1)
+    @get:IntRange(from = -1)
     public inline val lastIndex: Int
         get() = list.lastIndex
 
     /** Returns an [IntRange] of the valid indices for this [ColorList]. */
-    public inline val indices: IntRange
+    public inline val indices: kotlin.ranges.IntRange
         get() = list.indices
 
     /** Returns `true` if the collection has no elements in it. */
@@ -541,14 +542,14 @@
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline operator fun get(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun elementAt(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
@@ -560,7 +561,7 @@
      *   index not in the list.
      */
     public inline fun elementAtOrElse(
-        @androidx.annotation.IntRange(from = 0) index: Int,
+        @IntRange(from = 0) index: Int,
         defaultValue: (index: Int) -> Color
     ): Color = Color(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
 
@@ -641,7 +642,7 @@
      *
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun add(@androidx.annotation.IntRange(from = 0) index: Int, element: Color) =
+    public inline fun add(@IntRange(from = 0) index: Int, element: Color) =
         list.add(index, element.value.toLong())
 
     /**
@@ -651,10 +652,8 @@
      * @return `true` if the [MutableColorList] was changed or `false` if [elements] was empty
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun addAll(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        elements: ColorList
-    ): Boolean = list.addAll(index, elements.list)
+    public inline fun addAll(@IntRange(from = 0) index: Int, elements: ColorList): Boolean =
+        list.addAll(index, elements.list)
 
     /**
      * Adds all [elements] to the [MutableColorList] at the given [index], shifting over any
@@ -663,10 +662,8 @@
      * @return `true` if the [MutableColorList] was changed or `false` if [elements] was empty
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun addAll(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        elements: MutableColorList
-    ): Boolean = list.addAll(index, elements.list)
+    public inline fun addAll(@IntRange(from = 0) index: Int, elements: MutableColorList): Boolean =
+        list.addAll(index, elements.list)
 
     /**
      * Adds all [elements] to the end of the [MutableColorList] and returns `true` if the
@@ -747,7 +744,7 @@
      *
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
      */
-    public inline fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun removeAt(@IntRange(from = 0) index: Int): Color =
         Color(list.removeAt(index).toULong())
 
     /**
@@ -756,10 +753,8 @@
      * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
      * @throws IllegalArgumentException if [start] is greater than [end]
      */
-    public inline fun removeRange(
-        @androidx.annotation.IntRange(from = 0) start: Int,
-        @androidx.annotation.IntRange(from = 0) end: Int
-    ) = list.removeRange(start, end)
+    public inline fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) =
+        list.removeRange(start, end)
 
     /**
      * Keeps only [elements] in the [MutableColorList] and removes all other values.
@@ -781,10 +776,8 @@
      * @return the previous value set at [index]
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
      */
-    public inline operator fun set(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        element: Color
-    ): Color = Color(list.set(index, element.value.toLong()).toULong())
+    public inline operator fun set(@IntRange(from = 0) index: Int, element: Color): Color =
+        Color(list.set(index, element.value.toLong()).toULong())
 }
 
 /** @return a read-only [ColorList] with nothing in it. */
@@ -833,3 +826,35 @@
     MutableColorList(
         mutableLongListOf(element1.value.toLong(), element2.value.toLong(), element3.value.toLong())
     )
+
+/**
+ * Builds a new [ColorList] by populating a [MutableColorList] using the given [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableColorList] can be populated.
+ */
+internal inline fun buildColorList(
+    builderAction: MutableColorList.() -> Unit,
+): ColorList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorList().apply(builderAction).asColorList()
+}
+
+/**
+ * Builds a new [ColorList] by populating a [MutableColorList] using the given [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableColorList] can be populated.
+ */
+internal inline fun buildColorList(
+    initialCapacity: Int,
+    builderAction: MutableColorList.() -> Unit,
+): ColorList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorList(initialCapacity).apply(builderAction).asColorList()
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
index 20479137..4ceecf1 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
@@ -24,15 +24,18 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.compose.foundation.demos.collection
 
+import androidx.annotation.IntRange
 import androidx.collection.LongSet
 import androidx.collection.MutableLongSet
 import androidx.collection.emptyLongSet
 import androidx.collection.mutableLongSetOf
 import androidx.compose.ui.graphics.Color
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -103,6 +106,35 @@
     )
 
 /**
+ * Builds a new [ColorSet] by populating a [MutableColorSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ */
+internal inline fun buildColorSet(
+    builderAction: MutableColorSet.() -> Unit,
+): ColorSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorSet().apply(builderAction).asColorSet()
+}
+
+/**
+ * Builds a new [ColorSet] by populating a [MutableColorSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ */
+internal inline fun buildColorSet(
+    initialCapacity: Int,
+    builderAction: MutableColorSet.() -> Unit,
+): ColorSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorSet(initialCapacity).apply(builderAction).asColorSet()
+}
+
+/**
  * [ColorSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
@@ -116,19 +148,18 @@
  *
  * @see [MutableColorSet]
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class ColorSet(val set: LongSet) {
     /**
      * Returns the number of elements that can be stored in this set without requiring internal
      * storage reallocation.
      */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val capacity: Int
         get() = set.capacity
 
     /** Returns the number of elements in this set. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = set.size
 
@@ -200,7 +231,7 @@
     }
 
     /** Returns the number of elements in this set. */
-    @androidx.annotation.IntRange(from = 0) public inline fun count(): Int = set.count()
+    @IntRange(from = 0) public inline fun count(): Int = set.count()
 
     /**
      * Returns the number of elements matching the given [predicate].
@@ -208,7 +239,7 @@
      * @param predicate Called for all elements in the set to count the number for which it returns
      *   `true`.
      */
-    @androidx.annotation.IntRange(from = 0)
+    @IntRange(from = 0)
     public inline fun count(predicate: (element: Color) -> Boolean): Int {
         contract { callsInPlace(predicate) }
         return set.count { predicate(Color(it.toULong())) }
@@ -256,19 +287,18 @@
  * and one or more threads modify the structure of the set (insertion or removal for instance), the
  * calling code must provide the appropriate synchronization. Concurrent reads are however safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class MutableColorSet(val set: MutableLongSet) {
     /**
      * Returns the number of elements that can be stored in this set without requiring internal
      * storage reallocation.
      */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val capacity: Int
         get() = set.capacity
 
     /** Returns the number of elements in this set. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = set.size
 
@@ -340,7 +370,7 @@
     }
 
     /** Returns the number of elements in this set. */
-    @androidx.annotation.IntRange(from = 0) public inline fun count(): Int = set.count()
+    @IntRange(from = 0) public inline fun count(): Int = set.count()
 
     /**
      * Returns the number of elements matching the given [predicate].
@@ -348,7 +378,7 @@
      * @param predicate Called for all elements in the set to count the number for which it returns
      *   `true`.
      */
-    @androidx.annotation.IntRange(from = 0)
+    @IntRange(from = 0)
     public inline fun count(predicate: (element: Color) -> Boolean): Int {
         contract { callsInPlace(predicate) }
         return set.count { predicate(Color(it.toULong())) }
@@ -485,5 +515,5 @@
      * Returns the number of empty elements removed from this set's storage. Returns 0 if no
      * trimming is necessary or possible.
      */
-    @androidx.annotation.IntRange(from = 0) public inline fun trim(): Int = set.trim()
+    @IntRange(from = 0) public inline fun trim(): Int = set.trim()
 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
index 65d6c77..bc32206 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.demos.text
 
 import androidx.compose.foundation.border
-import androidx.compose.foundation.demos.collection.MutableColorList
+import androidx.compose.foundation.demos.collection.buildColorList
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -61,16 +61,14 @@
 
 // red is used for the selection container color
 private val Rainbow =
-    MutableColorList(initialCapacity = 6)
-        .apply {
-            add(Orange)
-            add(Yellow)
-            add(Green)
-            add(Blue)
-            add(Indigo)
-            add(Purple)
-        }
-        .asColorList()
+    buildColorList(initialCapacity = 6) {
+        add(Orange)
+        add(Yellow)
+        add(Green)
+        add(Blue)
+        add(Indigo)
+        add(Purple)
+    }
 
 @Composable
 fun MinTouchTargetTextSelection() {
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index 84d022c..59530fe 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -47,6 +47,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -418,6 +419,7 @@
         rule.runOnIdle { assertThat(remeasuresCount).isEqualTo(1) }
     }
 
+    @Ignore("b/369188686")
     @Test
     fun nodeIsReusedWhenRemovedFirst() {
         var itemCount by mutableStateOf(1)
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 5eb45a6..d2cea9b 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -59,6 +59,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.testutils.assertPixels
 import androidx.compose.testutils.assertShape
@@ -119,6 +120,7 @@
 import java.util.concurrent.CountDownLatch
 import kotlin.math.abs
 import kotlin.math.roundToInt
+import kotlin.random.Random
 import kotlin.test.assertEquals
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -2789,6 +2791,36 @@
         rule.onNodeWithTag(LazyListTag).assertStartPositionInRootIsEqualTo(itemSize)
     }
 
+    @Test
+    fun reorderingInLookeahead() {
+        var items by mutableStateOf(List(500) { it })
+
+        val itemSizePx = 50f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+
+        rule.setContent {
+            LookaheadScope {
+                LazyColumnOrRow(Modifier.mainAxisSize(itemSize * 2)) {
+                    items(items, key = { it }) {
+                        Box(Modifier.animateItem().mainAxisSize(itemSize)) {
+                            Box { BasicText("Item $it") }
+                        }
+                    }
+                }
+            }
+        }
+
+        val random = Random(42)
+        repeat(20) {
+            val newItems = items.shuffled(random)
+            items = newItems
+            rule.runOnUiThread {
+                Snapshot.sendApplyNotifications()
+                rule.mainClock.advanceTimeByFrame()
+            }
+        }
+    }
+
     // ********************* END OF TESTS *********************
     // Helper functions, etc. live below here
 
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt
new file mode 100644
index 0000000..d418c0a
--- /dev/null
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.samples
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.layout.LazyLayout
+import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
+import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.dp
+
+private val Items = (0..100).toList().map { it.toString() }
+
+/**
+ * In this example, the lazy layout simply lays out content based on its visibility on the screen.
+ */
+@Composable
+@Preview
+fun LazyLayoutDisplayVisibleItemsOnlySample() {
+    BasicNonScrollableLazyLayout(
+        modifier = Modifier.size(500.dp),
+        orientation = Orientation.Vertical,
+        items = Items
+    ) { item, index ->
+        Box(
+            modifier =
+                Modifier.width(100.dp)
+                    .height(100.dp)
+                    .background(color = if (index % 2 == 0) Color.Red else Color.Green)
+        ) {
+            Text(text = item)
+        }
+    }
+}
+
+/**
+ * A simple Layout that will place items top to down, or right to left, without any scrolling or
+ * offset until they fit the viewport.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun <T> BasicNonScrollableLazyLayout(
+    items: List<T>,
+    modifier: Modifier = Modifier,
+    orientation: Orientation = Orientation.Vertical,
+    content: @Composable (item: T, index: Int) -> Unit
+) {
+    val measurePolicy = remember(items, orientation) { basicMeasurePolicy(orientation, items.size) }
+    val itemProvider = remember(items, content) { { BasicLazyLayoutItemProvider(items, content) } }
+    LazyLayout(modifier = modifier, itemProvider = itemProvider, measurePolicy = measurePolicy)
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun basicMeasurePolicy(
+    orientation: Orientation,
+    itemCount: Int
+): LazyLayoutMeasureScope.(Constraints) -> MeasureResult = { constraints ->
+    fun Placeable.mainAxisSize() = if (orientation == Orientation.Vertical) height else width
+    fun Placeable.crossAxisSize() = if (orientation == Orientation.Vertical) width else height
+
+    val viewportSize =
+        if (orientation == Orientation.Vertical) constraints.maxHeight else constraints.maxWidth
+
+    val childConstraints =
+        Constraints(
+            maxWidth =
+                if (orientation == Orientation.Vertical) viewportSize else Constraints.Infinity,
+            maxHeight =
+                if (orientation == Orientation.Horizontal) viewportSize else Constraints.Infinity
+        )
+
+    var currentItemIndex = 0
+    // saves placeables and their main axis position
+    val placeables = mutableListOf<Pair<Placeable, Int>>()
+    var crossAxisSize = 0
+    var mainAxisSize = 0
+
+    // measure items until we either fill in the space or run out of items.
+    while (mainAxisSize < viewportSize && currentItemIndex < itemCount) {
+        val itemPlaceables = measure(currentItemIndex, childConstraints)
+        for (item in itemPlaceables) {
+            // save placeable to be placed later.
+            placeables.add(item to mainAxisSize)
+
+            mainAxisSize += item.mainAxisSize() // item size contributes to main axis size
+            // cross axis size will the size of tallest/widest item
+            crossAxisSize = maxOf(crossAxisSize, item.crossAxisSize())
+        }
+        currentItemIndex++
+    }
+
+    val layoutWidth =
+        if (orientation == Orientation.Horizontal) {
+            minOf(mainAxisSize, viewportSize)
+        } else {
+            crossAxisSize
+        }
+    val layoutHeight =
+        if (orientation == Orientation.Vertical) {
+            minOf(mainAxisSize, viewportSize)
+        } else {
+            crossAxisSize
+        }
+
+    layout(layoutWidth, layoutHeight) {
+        // since this is a linear list all items are placed on the same cross-axis position
+        for ((placeable, position) in placeables) {
+            if (orientation == Orientation.Vertical) {
+                placeable.place(0, position)
+            } else {
+                placeable.place(position, 0)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class BasicLazyLayoutItemProvider<T>(
+    private val items: List<T>,
+    private val content: @Composable (item: T, index: Int) -> Unit
+) : LazyLayoutItemProvider {
+    override val itemCount: Int = items.size
+
+    @Composable
+    override fun Item(index: Int, key: Any) {
+        content.invoke(items[index], index)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 4e75878..fb71215 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -55,6 +55,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.currentComposer
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -129,6 +130,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.abs
+import kotlin.math.absoluteValue
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -141,7 +143,6 @@
 import org.junit.After
 import org.junit.Assert
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -1849,6 +1850,66 @@
     }
 
     @Test
+    fun scrollable_nestedFling_parentShouldFlingWithVelocityLeft_whenInnerDisappears() {
+        var postFlingCalled = false
+        var postFlingAvailableVelocity = Velocity.Zero
+        var postFlingConsumedVelocity = Velocity.Zero
+        var flingDelta by mutableFloatStateOf(0.0f)
+        var preFlingVelocity = Velocity.Zero
+
+        val topConnection =
+            object : NestedScrollConnection {
+                override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                    // accumulate deltas for second fling only
+                    if (source == NestedScrollSource.SideEffect) {
+                        flingDelta += available.y
+                    }
+                    return super.onPreScroll(available, source)
+                }
+
+                override suspend fun onPreFling(available: Velocity): Velocity {
+                    preFlingVelocity = available
+                    return super.onPreFling(available)
+                }
+
+                override suspend fun onPostFling(
+                    consumed: Velocity,
+                    available: Velocity
+                ): Velocity {
+                    postFlingCalled = true
+                    postFlingAvailableVelocity = available
+                    postFlingConsumedVelocity = consumed
+                    return super.onPostFling(consumed, available)
+                }
+            }
+
+        val columnState = ScrollState(with(rule.density) { (50 * 200.dp).roundToPx() })
+
+        rule.setContent {
+            Box(Modifier.nestedScroll(topConnection)) {
+                if (flingDelta.absoluteValue < 100) {
+                    Column(Modifier.testTag("column").verticalScroll(columnState)) {
+                        repeat(100) { Box(Modifier.size(200.dp)) }
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("column").performTouchInput { swipeUp() }
+        rule.waitForIdle()
+        // removed scrollable
+        rule.onNodeWithTag("column").assertDoesNotExist()
+        rule.runOnIdle {
+            // we fired a post fling call after the disappearance
+            assertThat(postFlingCalled).isTrue()
+
+            // fling velocity in onPostFling is correctly propagated
+            assertThat(postFlingConsumedVelocity + postFlingAvailableVelocity)
+                .isEqualTo(preFlingVelocity)
+        }
+    }
+
+    @Test
     fun scrollable_bothOrientations_proxiesPostFling() {
         val velocityFlung = 5000f
         val outerState = ScrollableState(consumeScrollDelta = { 0f })
@@ -2521,77 +2582,6 @@
     }
 
     @Test
-    @Ignore("b/175010956") // re-enable when we come back to fling continuation fix
-    fun nestedScrollable_shouldImmediateScrollIfChildIsFlinging() {
-        var innerDelta = 0f
-        var middleDelta = 0f
-        var outerDelta = 0f
-        var touchSlop = 0f
-
-        val outerStateController = ScrollableState {
-            outerDelta += it
-            0f
-        }
-
-        val middleController = ScrollableState {
-            middleDelta += it
-            0f
-        }
-
-        val innerController = ScrollableState {
-            innerDelta += it
-            it / 2f
-        }
-
-        rule.setContentAndGetScope {
-            touchSlop = LocalViewConfiguration.current.touchSlop
-            Box(
-                modifier =
-                    Modifier.testTag("outerScrollable")
-                        .size(600.dp)
-                        .background(Color.Red)
-                        .scrollable(outerStateController, orientation = Orientation.Vertical),
-                contentAlignment = Alignment.BottomStart
-            ) {
-                Box(
-                    modifier =
-                        Modifier.testTag("middleScrollable")
-                            .size(300.dp)
-                            .background(Color.Blue)
-                            .scrollable(middleController, orientation = Orientation.Vertical),
-                    contentAlignment = Alignment.BottomStart
-                ) {
-                    Box(
-                        modifier =
-                            Modifier.testTag("innerScrollable")
-                                .size(50.dp)
-                                .background(Color.Yellow)
-                                .scrollable(innerController, orientation = Orientation.Vertical)
-                    )
-                }
-            }
-        }
-
-        rule.mainClock.autoAdvance = false
-        rule.onNodeWithTag("innerScrollable").performTouchInput { swipeUp() }
-
-        rule.mainClock.advanceTimeByFrame()
-        rule.mainClock.advanceTimeByFrame()
-
-        val previousOuter = outerDelta
-
-        rule.onNodeWithTag("outerScrollable").performTouchInput {
-            down(topCenter)
-            // Move less than touch slop, should start immediately
-            moveBy(Offset(0f, touchSlop / 2))
-        }
-
-        rule.mainClock.autoAdvance = true
-
-        rule.runOnIdle { assertThat(outerDelta).isEqualTo(previousOuter + touchSlop / 2) }
-    }
-
-    @Test
     fun nestedScrollable_noFlingContinuationInCrossAxis_shouldAllowClicksOnCrossAxis_scrollable() {
         var clicked = 0
         rule.setContentAndGetScope {
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 f81e6db..5d6a2da 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
@@ -298,6 +298,16 @@
             reverseDirection = reverseDirection,
             flingBehavior = flingBehavior ?: defaultFlingBehavior,
             nestedScrollDispatcher = nestedScrollDispatcher,
+            shouldCancelFling = { flingPixels ->
+                // fling should be cancelled if we try to scroll more than we can or if this node
+                // is detached during a fling.
+                // tries to scroll forward but cannot.
+                (flingPixels > 0.0f && !state.canScrollForward) ||
+                    // tries to scroll backward but cannot.
+                    (flingPixels < 0.0f && !state.canScrollBackward) ||
+                    // node is detached.
+                    !isAttached
+            }
         )
 
     private val nestedScrollConnection =
@@ -340,12 +350,7 @@
 
     @OptIn(ExperimentalFoundationApi::class)
     override fun onDragStopped(velocity: Velocity) {
-        if (NewNestedFlingPropagationEnabled) {
-            if (!isAttached) return
-            coroutineScope.launch { scrollingLogic.onDragStopped(velocity) }
-        } else {
-            nestedScrollDispatcher.coroutineScope.launch { scrollingLogic.onDragStopped(velocity) }
-        }
+        nestedScrollDispatcher.coroutineScope.launch { scrollingLogic.onDragStopped(velocity) }
     }
 
     override fun startDragImmediately(): Boolean {
@@ -597,6 +602,7 @@
     private var orientation: Orientation,
     private var reverseDirection: Boolean,
     private var nestedScrollDispatcher: NestedScrollDispatcher,
+    private val shouldCancelFling: (Float) -> Boolean
 ) {
 
     fun Float.toOffset(): Offset =
@@ -717,20 +723,14 @@
             val reverseScope =
                 object : ScrollScope {
                     override fun scrollBy(pixels: Float): Float {
-                        // Fling has hit the bounds, cancel it to allow continuation. This will
-                        // conclude this node's fling, allowing the onPostFling signal to be called
+                        // Fling has hit the bounds or node left composition,
+                        // cancel it to allow continuation. This will conclude this node's fling,
+                        // allowing the onPostFling signal to be called
                         // with the leftover velocity from the fling animation. Any nested scroll
                         // node above will be able to pick up the left over velocity and continue
                         // the fling.
-                        if (NewNestedFlingPropagationEnabled) {
-                            if (
-                                pixels > 0.0f && !scrollableState.canScrollForward ||
-                                    pixels < 0.0f && !scrollableState.canScrollBackward
-                            ) {
-                                throw kotlin.coroutines.cancellation.CancellationException(
-                                    "The fling was cancelled"
-                                )
-                            }
+                        if (NewNestedFlingPropagationEnabled && shouldCancelFling(pixels)) {
+                            throw FlingCancellationException()
                         }
 
                         return nestedScrollScope
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
index 3cb700c..2c1bc66 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.ReusableContentHost
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.saveable.SaveableStateHolder
 import kotlin.jvm.JvmInline
@@ -94,7 +93,7 @@
                     if (index != -1) this.index = index
                 }
 
-                ReusableContentHost(active = index != -1) {
+                if (index != -1) {
                     SkippableItem(
                         itemProvider,
                         StableValue(saveableStateHolder),
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 81a4eb3..d6cf4ec 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
@@ -37,7 +37,7 @@
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.fastJoinToString
-import androidx.compose.ui.util.fastMaxOfOrNull
+import androidx.compose.ui.util.fastMaxOfOrDefault
 import androidx.compose.ui.util.fastRoundToInt
 import androidx.compose.ui.util.packInts
 import androidx.compose.ui.util.unpackInt1
@@ -1198,14 +1198,14 @@
     override fun getParentData(index: Int) = placeables[index].parentData
 
     val mainAxisSize: Int =
-        placeables.fastMaxOfOrNull { placeable ->
+        placeables.fastMaxOfOrDefault(0) { placeable ->
             if (isVertical) placeable.height else placeable.width
-        } ?: 0
+        }
 
     override val mainAxisSizeWithSpacings: Int = (mainAxisSize + spacing).coerceAtLeast(0)
 
     val crossAxisSize: Int =
-        placeables.fastMaxOfOrNull { if (isVertical) it.width else it.height } ?: 0
+        placeables.fastMaxOfOrDefault(0) { if (isVertical) it.width else it.height }
 
     private var mainAxisLayoutSize: Int = Unset
     private var minMainAxisOffset: Int = 0
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
index 48d0d2e..c68c442 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
@@ -19,7 +19,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.util.fastFold
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import kotlin.math.max
@@ -31,14 +30,15 @@
 @Composable
 internal fun SimpleLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
     Layout(modifier = modifier, content = content) { measurables, constraints ->
-        val placeables = measurables.fastMap { measurable -> measurable.measure(constraints) }
-
-        val width =
-            placeables.fastFold(0) { maxWidth, placeable -> max(maxWidth, (placeable.width)) }
-
-        val height =
-            placeables.fastFold(0) { minWidth, placeable -> max(minWidth, (placeable.height)) }
-
+        var width = 0
+        var height = 0
+        val placeables =
+            measurables.fastMap { measurable ->
+                val placeable = measurable.measure(constraints)
+                width = max(width, placeable.width)
+                height = max(height, placeable.height)
+                placeable
+            }
         layout(width, height) { placeables.fastForEach { placeable -> placeable.place(0, 0) } }
     }
 }
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
index 84183af..531c84a 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -21,7 +21,7 @@
 import com.intellij.psi.PsiParameter
 import com.intellij.psi.impl.compiled.ClsParameterImpl
 import com.intellij.psi.impl.light.LightParameter
-import kotlinx.metadata.jvm.annotations
+import kotlin.metadata.jvm.annotations
 import org.jetbrains.kotlin.psi.KtAnnotated
 import org.jetbrains.kotlin.psi.KtFunction
 import org.jetbrains.kotlin.psi.KtProperty
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
index 1425e05..ec7d114 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
@@ -24,11 +24,11 @@
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.impl.compiled.ClsMethodImpl
 import com.intellij.psi.util.ClassUtil
-import kotlinx.metadata.KmDeclarationContainer
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.Metadata
-import kotlinx.metadata.jvm.signature
+import kotlin.metadata.KmDeclarationContainer
+import kotlin.metadata.KmFunction
+import kotlin.metadata.jvm.KotlinClassMetadata
+import kotlin.metadata.jvm.Metadata
+import kotlin.metadata.jvm.signature
 
 /**
  * @return the corresponding [KmFunction] for this [PsiMethod], or `null` if there is no
@@ -60,7 +60,7 @@
         } catch (e: Exception) {
             // Don't crash if we are trying to parse metadata from a newer version of Kotlin, than
             // is
-            // supported by the bundled version of kotlinx-metadata-jvm
+            // supported by the bundled version of kotlin-metadata-jvm
             return null
         }
 
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index b9478f3..81b56b6 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.lint
 
-import kotlinx.metadata.ClassName
+import kotlin.metadata.ClassName
 
 /** Contains common names used for lint checks. */
 object Names {
@@ -121,7 +121,7 @@
         get() = pkg.segments.joinToString(".", postfix = ".") + nameSegments.joinToString(".")
 
     /**
-     * The [ClassName] for use with kotlinx.metadata. Note that in kotlinx.metadata the actual type
+     * The [ClassName] for use with kotlin.metadata. Note that in kotlin.metadata the actual type
      * might be different from the underlying JVM type, for example: kotlin/Int -> java/lang/Integer
      */
     val kmClassName: ClassName
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
index a15c376..b925fda 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
@@ -28,7 +28,7 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.impl.compiled.ClsMethodImpl
-import kotlinx.metadata.KmClassifier
+import kotlin.metadata.KmClassifier
 import org.jetbrains.kotlin.asJava.unwrapped
 import org.jetbrains.kotlin.psi.KtForExpression
 import org.jetbrains.kotlin.psi.KtNamedFunction
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 74fbeb5..b09f73b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -55,6 +55,19 @@
     field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class MutableThreePaneScaffoldState extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState {
+    ctor public MutableThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
+    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public float progressFraction;
+    property public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
     field public static final androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion Companion;
   }
@@ -125,6 +138,30 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
     method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
     method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+    field public static final androidx.compose.material3.adaptive.layout.PaneMotion.Companion Companion;
+  }
+
+  public static final class PaneMotion.Companion {
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getAnimateBounds();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeftDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRightDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterWithExpand();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitWithShrink();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getNoMotion();
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion AnimateBounds;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeftDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRightDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterWithExpand;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitWithShrink;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion NoMotion;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
@@ -283,17 +320,13 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class ThreePaneScaffoldState {
-    ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
-    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
-    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
-    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
-    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
-    property @FloatRange(from=0.0, to=1.0) public final float progressFraction;
-    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public abstract sealed class ThreePaneScaffoldState {
+    method public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public abstract float getProgressFraction();
+    method public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public abstract float progressFraction;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider androidx.compose.material3.adaptive.layout.PaneScaffoldValue<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole> {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 74fbeb5..b09f73b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -55,6 +55,19 @@
     field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class MutableThreePaneScaffoldState extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState {
+    ctor public MutableThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
+    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public float progressFraction;
+    property public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
     field public static final androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion Companion;
   }
@@ -125,6 +138,30 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
     method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
     method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+    field public static final androidx.compose.material3.adaptive.layout.PaneMotion.Companion Companion;
+  }
+
+  public static final class PaneMotion.Companion {
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getAnimateBounds();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeftDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRightDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterWithExpand();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitWithShrink();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getNoMotion();
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion AnimateBounds;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeftDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRightDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterWithExpand;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitWithShrink;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion NoMotion;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
@@ -283,17 +320,13 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class ThreePaneScaffoldState {
-    ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
-    method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
-    method @FloatRange(from=0.0, to=1.0) public float getProgressFraction();
-    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
-    method public suspend Object? seekTo(@FloatRange(from=0.0, to=1.0) float fraction, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? snapTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
-    property @FloatRange(from=0.0, to=1.0) public final float progressFraction;
-    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public abstract sealed class ThreePaneScaffoldState {
+    method public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
+    method @FloatRange(from=0.0, to=1.0) public abstract float getProgressFraction();
+    method public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getTargetState();
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue currentState;
+    property @FloatRange(from=0.0, to=1.0) public abstract float progressFraction;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider androidx.compose.material3.adaptive.layout.PaneScaffoldValue<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole> {
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt
index 4626533..e716554 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt
@@ -51,7 +51,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress0() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValueSinglePane) }
         }
@@ -67,7 +67,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress10() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.1f, MockTargetScaffoldValueSinglePane) }
         }
@@ -83,7 +83,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress15() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.15f, MockTargetScaffoldValueSinglePane) }
         }
@@ -99,7 +99,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress20() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.2f, MockTargetScaffoldValueSinglePane) }
         }
@@ -115,7 +115,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress50() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.5f, MockTargetScaffoldValueSinglePane) }
         }
@@ -131,7 +131,7 @@
     @Test
     fun singlePaneLayout_defaultPaneMotion_progress100() {
         rule.setContent {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValueSinglePane) }
         }
@@ -147,7 +147,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress0() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValuePaneSwitching) }
         }
@@ -163,7 +163,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress10() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) {
                 scaffoldState.seekTo(0.1f, MockTargetScaffoldValuePaneSwitching)
@@ -181,7 +181,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress15() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) {
                 scaffoldState.seekTo(0.15f, MockTargetScaffoldValuePaneSwitching)
@@ -199,7 +199,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress20() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) {
                 scaffoldState.seekTo(0.2f, MockTargetScaffoldValuePaneSwitching)
@@ -217,7 +217,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress50() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) {
                 scaffoldState.seekTo(0.5f, MockTargetScaffoldValuePaneSwitching)
@@ -235,7 +235,7 @@
     @Test
     fun dualPaneLayout_defaultPaneSwitching_progress100() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValuePaneSwitching) }
         }
@@ -251,7 +251,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress0() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValuePaneShifting) }
         }
@@ -267,7 +267,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress10() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.1f, MockTargetScaffoldValuePaneShifting) }
         }
@@ -283,7 +283,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress15() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) {
                 scaffoldState.seekTo(0.15f, MockTargetScaffoldValuePaneShifting)
@@ -301,7 +301,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress20() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.2f, MockTargetScaffoldValuePaneShifting) }
         }
@@ -317,7 +317,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress50() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(0.5f, MockTargetScaffoldValuePaneShifting) }
         }
@@ -333,7 +333,7 @@
     @Test
     fun dualPaneLayout_defaultPaneShifting_progress100() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
-            val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+            val scaffoldState = MutableThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
             SampleThreePaneScaffold(scaffoldState)
             LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValuePaneShifting) }
         }
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
index 10d5ba2..a55e8ba 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
@@ -23,18 +23,18 @@
 import androidx.compose.animation.slideInHorizontally
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.AnimateBounds
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromLeft
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromLeftDelayed
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromRight
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromRightDelayed
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterWithExpand
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitToLeft
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitToRight
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitWithShrink
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.NoMotion
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion.Expanded
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion.Hidden
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.AnimateBounds
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromLeft
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromLeftDelayed
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromRight
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromRightDelayed
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterWithExpand
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitToLeft
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitToRight
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitWithShrink
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.NoMotion
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -79,7 +79,7 @@
         ExitWithShrink.assertTransitions(EnterTransition.None, mockExitWithShrinkTransition)
     }
 
-    private fun DefaultPaneMotion.assertTransitions(
+    private fun PaneMotion.assertTransitions(
         expectedEnterTransition: EnterTransition,
         expectedExitTransition: ExitTransition
     ) {
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
index df65278..7059197 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
@@ -43,7 +43,7 @@
     modifier: Modifier = Modifier,
     content: (@Composable AnimatedPaneScope.() -> Unit),
 ) {
-    val animatingBounds = paneMotion == DefaultPaneMotion.AnimateBounds
+    val animatingBounds = paneMotion == PaneMotion.AnimateBounds
     val motionProgress = { motionProgress }
     scaffoldStateTransition.AnimatedVisibility(
         visible = { value: T -> value[paneRole] != PaneAdaptedValue.Hidden },
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
index 58798db..8d99c09 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
@@ -97,7 +97,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 class PaneMotionData internal constructor() {
-    var motion: PaneMotion = DefaultPaneMotion.NoMotion
+    var motion: PaneMotion = PaneMotion.NoMotion
         internal set
 
     var currentSize: IntSize = IntSize.Zero
@@ -119,8 +119,8 @@
         // Find the right edge offset of the rightmost pane that enters from its left
         paneMotionDataList.fastForEachReversed {
             if (
-                it.motion == DefaultPaneMotion.EnterFromLeft ||
-                    it.motion == DefaultPaneMotion.EnterFromLeftDelayed
+                it.motion == PaneMotion.EnterFromLeft ||
+                    it.motion == PaneMotion.EnterFromLeftDelayed
             ) {
                 return -it.targetPosition.x - it.targetSize.width
             }
@@ -134,8 +134,8 @@
         // Find the left edge offset of the leftmost pane that enters from its right
         paneMotionDataList.fastForEach {
             if (
-                it.motion == DefaultPaneMotion.EnterFromRight ||
-                    it.motion == DefaultPaneMotion.EnterFromRightDelayed
+                it.motion == PaneMotion.EnterFromRight ||
+                    it.motion == PaneMotion.EnterFromRightDelayed
             ) {
                 return scaffoldSize.width - it.targetPosition.x
             }
@@ -148,7 +148,7 @@
     get() {
         // Find the right edge offset of the rightmost pane that exits to its left
         paneMotionDataList.fastForEachReversed {
-            if (it.motion == DefaultPaneMotion.ExitToLeft) {
+            if (it.motion == PaneMotion.ExitToLeft) {
                 return -it.currentPosition.x - it.currentSize.width
             }
         }
@@ -160,7 +160,7 @@
     get() {
         // Find the left edge offset of the leftmost pane that exits to its right
         paneMotionDataList.fastForEach {
-            if (it.motion == DefaultPaneMotion.ExitToRight) {
+            if (it.motion == PaneMotion.ExitToRight) {
                 return scaffoldSize.width - it.currentPosition.x
             }
         }
@@ -175,66 +175,116 @@
 
     /** The [ExitTransition] of a pane under the given [PaneScaffoldMotionScope] */
     val PaneScaffoldMotionScope.exitTransition: ExitTransition
-}
 
-@ExperimentalMaterial3AdaptiveApi
-@JvmInline
-internal value class DefaultPaneMotion private constructor(val value: Int) : PaneMotion {
-    companion object {
-        val NoMotion = DefaultPaneMotion(0)
-        val AnimateBounds = DefaultPaneMotion(1)
-        val EnterFromLeft = DefaultPaneMotion(2)
-        val EnterFromRight = DefaultPaneMotion(3)
-        val EnterFromLeftDelayed = DefaultPaneMotion(4)
-        val EnterFromRightDelayed = DefaultPaneMotion(5)
-        val ExitToLeft = DefaultPaneMotion(6)
-        val ExitToRight = DefaultPaneMotion(7)
-        val EnterWithExpand = DefaultPaneMotion(8)
-        val ExitWithShrink = DefaultPaneMotion(9)
+    private abstract class DefaultImpl(val name: String) : PaneMotion {
+        override val PaneScaffoldMotionScope.enterTransition
+            get() = EnterTransition.None
+
+        override val PaneScaffoldMotionScope.exitTransition
+            get() = ExitTransition.None
+
+        override fun toString() = name
     }
 
-    override val PaneScaffoldMotionScope.enterTransition: EnterTransition
-        get() =
-            when (this@DefaultPaneMotion) {
-                EnterFromLeft ->
-                    slideInHorizontally(positionAnimationSpec) { slideInFromLeftOffset }
-                EnterFromRight ->
-                    slideInHorizontally(positionAnimationSpec) { slideInFromRightOffset }
-                EnterFromLeftDelayed ->
-                    slideInHorizontally(delayedPositionAnimationSpec) { slideInFromLeftOffset }
-                EnterFromRightDelayed ->
-                    slideInHorizontally(delayedPositionAnimationSpec) { slideInFromRightOffset }
-                // TODO(conradche): Figure out how to expand with position change
-                EnterWithExpand ->
-                    expandHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
-                else -> EnterTransition.None
+    companion object {
+        /** The default pane motion that no animation will be performed. */
+        val NoMotion: PaneMotion = object : DefaultImpl("NoMotion") {}
+
+        /**
+         * The default pane motion that will animate panes bounds with the given animation specs
+         * during motion. Note that this should only be used when the associated pane is keeping
+         * showing during the motion.
+         */
+        val AnimateBounds: PaneMotion = object : DefaultImpl("AnimateBounds") {}
+
+        /**
+         * The default pane motion that will slide panes in from left. Note that this should only be
+         * used when the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromLeft: PaneMotion =
+            object : DefaultImpl("EnterFromLeft") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = slideInHorizontally(positionAnimationSpec) { slideInFromLeftOffset }
             }
 
-    override val PaneScaffoldMotionScope.exitTransition: ExitTransition
-        get() =
-            when (this@DefaultPaneMotion) {
-                ExitToLeft -> slideOutHorizontally(positionAnimationSpec) { slideOutToLeftOffset }
-                ExitToRight -> slideOutHorizontally(positionAnimationSpec) { slideOutToRightOffset }
-                // TODO(conradche): Figure out how to shrink with position change
-                ExitWithShrink ->
-                    shrinkHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
-                else -> ExitTransition.None
+        /**
+         * The default pane motion that will slide panes in from right. Note that this should only
+         * be used when the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromRight: PaneMotion =
+            object : DefaultImpl("EnterFromRight") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = slideInHorizontally(positionAnimationSpec) { slideInFromRightOffset }
             }
 
-    override fun toString(): String =
-        when (this) {
-            NoMotion -> "NoMotion"
-            AnimateBounds -> "AnimateBounds"
-            EnterFromLeft -> "EnterFromLeft"
-            EnterFromRight -> "EnterFromRight"
-            EnterFromLeftDelayed -> "EnterFromLeftDelayed"
-            EnterFromRightDelayed -> "EnterFromRightDelayed"
-            ExitToLeft -> "ExitToLeft"
-            ExitToRight -> "ExitToRight"
-            EnterWithExpand -> "EnterWithExpand"
-            ExitWithShrink -> "ExitWithShrink"
-            else -> "Undefined($value)"
-        }
+        /**
+         * The default pane motion that will slide panes in from left with a delay, usually to avoid
+         * the interference of other exiting panes. Note that this should only be used when the
+         * associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromLeftDelayed: PaneMotion =
+            object : DefaultImpl("EnterFromLeftDelayed") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() =
+                        slideInHorizontally(delayedPositionAnimationSpec) { slideInFromLeftOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes in from right with a delay, usually to
+         * avoid the interference of other exiting panes. Note that this should only be used when
+         * the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromRightDelayed: PaneMotion =
+            object : DefaultImpl("EnterFromRightDelayed") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() =
+                        slideInHorizontally(delayedPositionAnimationSpec) { slideInFromRightOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes out to left. Note that this should only be
+         * used when the associated pane is exiting - i.e. becoming hidden from a visible state.
+         */
+        val ExitToLeft: PaneMotion =
+            object : DefaultImpl("ExitToLeft") {
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = slideOutHorizontally(positionAnimationSpec) { slideOutToLeftOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes out to right. Note that this should only be
+         * used when the associated pane is exiting - i.e. becoming hidden from a visible state.
+         */
+        val ExitToRight: PaneMotion =
+            object : DefaultImpl("ExitToRight") {
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = slideOutHorizontally(positionAnimationSpec) { slideOutToRightOffset }
+            }
+
+        /**
+         * The default pane motion that will expand panes from a zero size. Note that this should
+         * only be used when the associated pane is entering - i.e. becoming visible from a hidden
+         * state.
+         */
+        val EnterWithExpand: PaneMotion =
+            object : DefaultImpl("EnterWithExpand") {
+                // TODO(conradchen): Expand with position change
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = expandHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
+            }
+
+        /**
+         * The default pane motion that will shrink panes until it's gone. Note that this should
+         * only be used when the associated pane is exiting - i.e. becoming hidden from a visible
+         * state.
+         */
+        val ExitWithShrink: PaneMotion =
+            object : DefaultImpl("ExitWithShrink") {
+                // TODO(conradchen): Shrink with position change
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = shrinkHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
+            }
+    }
 }
 
 @ExperimentalMaterial3AdaptiveApi
@@ -245,7 +295,7 @@
 ): List<PaneMotion> {
     val numOfPanes = paneOrder.size
     val paneStatus = Array(numOfPanes) { PaneMotionStatus.Hidden }
-    val paneMotions = MutableList<PaneMotion>(numOfPanes) { DefaultPaneMotion.NoMotion }
+    val paneMotions = MutableList(numOfPanes) { PaneMotion.NoMotion }
     var firstShownPaneIndex = numOfPanes
     var firstEnteringPaneIndex = numOfPanes
     var lastShownPaneIndex = -1
@@ -261,7 +311,7 @@
             PaneMotionStatus.Shown -> {
                 firstShownPaneIndex = min(firstShownPaneIndex, i)
                 lastShownPaneIndex = max(lastShownPaneIndex, i)
-                paneMotions[i] = DefaultPaneMotion.AnimateBounds
+                paneMotions[i] = PaneMotion.AnimateBounds
             }
             PaneMotionStatus.Entering -> {
                 firstEnteringPaneIndex = min(firstEnteringPaneIndex, i)
@@ -286,26 +336,26 @@
                     // No panes will interfere the motion on the right, exit to right.
                     hasPanesExitToRight = true
                     firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i)
-                    DefaultPaneMotion.ExitToRight
+                    PaneMotion.ExitToRight
                 } else if (!hasShownPanesOnLeft && !hasEnteringPanesOnLeft) {
                     // No panes will interfere the motion on the left, exit to left.
                     hasPanesExitToLeft = true
                     lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i)
-                    DefaultPaneMotion.ExitToLeft
+                    PaneMotion.ExitToLeft
                 } else if (!hasShownPanesOnRight) {
                     // Only showing panes can interfere the motion on the right, exit to right.
                     hasPanesExitToRight = true
                     firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i)
-                    DefaultPaneMotion.ExitToRight
+                    PaneMotion.ExitToRight
                 } else if (!hasShownPanesOnLeft) { // Only showing panes on left
                     // Only showing panes can interfere the motion on the left, exit to left.
                     hasPanesExitToLeft = true
                     lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i)
-                    DefaultPaneMotion.ExitToLeft
+                    PaneMotion.ExitToLeft
                 } else {
                     // Both sides has panes that keep being visible during transition, shrink to
                     // exit
-                    DefaultPaneMotion.ExitWithShrink
+                    PaneMotion.ExitWithShrink
                 }
         }
     }
@@ -326,20 +376,20 @@
             paneMotions[i] =
                 if (noBlockingPanesOnRight && !hasPanesExitToRight) {
                     // No panes will block the motion on the right, enter from right.
-                    DefaultPaneMotion.EnterFromRight
+                    PaneMotion.EnterFromRight
                 } else if (noBlockingPanesOnLeft && !hasPanesExitToLeft) {
                     // No panes will block the motion on the left, enter from left.
-                    DefaultPaneMotion.EnterFromLeft
+                    PaneMotion.EnterFromLeft
                 } else if (noBlockingPanesOnRight) {
                     // Only hiding panes can interfere the motion on the right, enter from right.
-                    DefaultPaneMotion.EnterFromRightDelayed
+                    PaneMotion.EnterFromRightDelayed
                 } else if (noBlockingPanesOnLeft) {
                     // Only hiding panes can interfere the motion on the left, enter from left.
-                    DefaultPaneMotion.EnterFromLeftDelayed
+                    PaneMotion.EnterFromLeftDelayed
                 } else {
                     // Both sides has panes that keep being visible during transition, expand to
                     // enter
-                    DefaultPaneMotion.EnterWithExpand
+                    PaneMotion.EnterWithExpand
                 }
         }
     }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
index 23a9c59..7924ad8 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
@@ -235,11 +235,7 @@
     companion object {
         /** A default [ThreePaneMotion] instance that specifies no motions. */
         val NoMotion =
-            ThreePaneMotion(
-                DefaultPaneMotion.NoMotion,
-                DefaultPaneMotion.NoMotion,
-                DefaultPaneMotion.NoMotion
-            )
+            ThreePaneMotion(PaneMotion.NoMotion, PaneMotion.NoMotion, PaneMotion.NoMotion)
     }
 }
 
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index ad081d7..c4346cf 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -85,7 +85,7 @@
         null,
     primaryPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
 ) {
-    val scaffoldState = remember { ThreePaneScaffoldState(scaffoldValue) }
+    val scaffoldState = remember { MutableThreePaneScaffoldState(scaffoldValue) }
     LaunchedEffect(key1 = scaffoldValue) { scaffoldState.animateTo(scaffoldValue) }
     ThreePaneScaffold(
         modifier = modifier,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
index 8454908..15420c9 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
@@ -51,7 +51,7 @@
     override val paneRole: ThreePaneScaffoldRole,
     scaffoldScope: ThreePaneScaffoldScope,
 ) : ThreePaneScaffoldPaneScope, ThreePaneScaffoldScope by scaffoldScope {
-    override var paneMotion: PaneMotion by mutableStateOf(DefaultPaneMotion.ExitToLeft)
+    override var paneMotion: PaneMotion by mutableStateOf(PaneMotion.ExitToLeft)
         private set
 
     fun updatePaneMotion(paneMotions: ThreePaneMotion) {
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
index 6363568..21077d3 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
@@ -27,33 +27,23 @@
 import androidx.compose.runtime.Stable
 
 /**
- * The state of a three pane scaffold. It serves as the [SeekableTransitionState] to manipulate the
- * [Transition] between [ThreePaneScaffoldValue]s.
+ * A read-only state of a three pane scaffold. It provides information about the [Transition]
+ * between [ThreePaneScaffoldValue]s.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Stable
-class ThreePaneScaffoldState
-internal constructor(
-    private val transitionState: SeekableTransitionState<ThreePaneScaffoldValue>,
-) {
-    /** Constructs a [ThreePaneScaffoldState] with an initial value of [initialScaffoldValue]. */
-    constructor(
-        initialScaffoldValue: ThreePaneScaffoldValue
-    ) : this(SeekableTransitionState(initialScaffoldValue))
-
+sealed class ThreePaneScaffoldState {
     /**
      * Current [ThreePaneScaffoldValue] state of the transition. If there is an active transition,
      * [currentState] and [targetState] are different.
      */
-    val currentState
-        get() = transitionState.currentState
+    abstract val currentState: ThreePaneScaffoldValue
 
     /**
      * Target [ThreePaneScaffoldValue] state of the transition. If this is the same as
      * [currentState], no transition is active.
      */
-    val targetState
-        get() = transitionState.targetState
+    abstract val targetState: ThreePaneScaffoldValue
 
     /**
      * The progress of the transition from [currentState] to [targetState] as a fraction of the
@@ -61,26 +51,46 @@
      *
      * If [targetState] and [currentState] are the same, [progressFraction] will be 0.
      */
+    @get:FloatRange(from = 0.0, to = 1.0) abstract val progressFraction: Float
+
+    @Composable internal abstract fun rememberTransition(): Transition<ThreePaneScaffoldValue>
+}
+
+/**
+ * The seekable state of a three pane scaffold. It serves as the [SeekableTransitionState] to
+ * manipulate the [Transition] between [ThreePaneScaffoldValue]s.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Stable
+class MutableThreePaneScaffoldState(initialScaffoldValue: ThreePaneScaffoldValue) :
+    ThreePaneScaffoldState() {
+    private val transitionState = SeekableTransitionState(initialScaffoldValue)
+
+    override val currentState
+        get() = transitionState.currentState
+
+    override val targetState
+        get() = transitionState.targetState
+
     @get:FloatRange(from = 0.0, to = 1.0)
-    val progressFraction
+    override val progressFraction
         get() = transitionState.fraction
 
     private val mutatorMutex = MutatorMutex()
 
     /**
-     * Creates a [Transition] and puts it in the [currentState] of this [ThreePaneScaffoldState]. If
-     * [targetState] changes, the [Transition] will change where it will animate to.
-     *
-     * @param label The optional label for the transition.
+     * Creates a [Transition] and puts it in the [currentState] of this
+     * [MutableThreePaneScaffoldState]. If [targetState] changes, the [Transition] will change where
+     * it will animate to.
      */
     @Composable
-    internal fun rememberTransition(label: String? = null): Transition<ThreePaneScaffoldValue> =
-        rememberTransition(transitionState, label)
+    override fun rememberTransition(): Transition<ThreePaneScaffoldValue> =
+        rememberTransition(transitionState, label = "ThreePaneScaffoldState")
 
     /**
-     * Sets [currentState] and [targetState][ThreePaneScaffoldState.targetState] to [targetState]
-     * and snaps all values to those at that state. The transition will not have any animations
-     * running after running [snapTo].
+     * Sets [currentState] and [targetState][MutableThreePaneScaffoldState.targetState] to
+     * [targetState] and snaps all values to those at that state. The transition will not have any
+     * animations running after running [snapTo].
      *
      * @param targetState The [ThreePaneScaffoldValue] state to snap to.
      * @see SeekableTransitionState.snapTo
@@ -105,8 +115,8 @@
     }
 
     /**
-     * Updates the current [targetState][ThreePaneScaffoldState.targetState] to [targetState] with
-     * an animation to the new state.
+     * Updates the current [targetState][MutableThreePaneScaffoldState.targetState] to [targetState]
+     * with an animation to the new state.
      *
      * @param targetState The [ThreePaneScaffoldValue] state to animate towards.
      * @param animationSpec If provided, is used to animate the animation fraction. If `null`, the
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt
index 2f2af3d..d119518 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt
@@ -47,7 +47,7 @@
  * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
  *   how many expanded panes can be shown at the same time.
  * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
- *   the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
+ *   the default value will be [ThreePaneScaffoldDefaults.adaptStrategies].
  * @param currentDestination The current destination item, which will be treated as having the
  *   highest priority, can be `null`.
  */
@@ -84,7 +84,7 @@
  * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.,
  *   how many expanded panes can be shown at the same time.
  * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
- *   the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
+ *   the default value will be [ThreePaneScaffoldDefaults.adaptStrategies].
  * @param destinationHistory The history of past destination items. The last destination will have
  *   the highest priority, and the second last destination will have the second highest priority,
  *   and so forth until all panes have a priority assigned. Note that the last destination is
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index 4612aff..6acaa74 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -25,15 +25,18 @@
     method public boolean canNavigateBack(optional String backNavigationBehavior);
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
     method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
     method public boolean isDestinationHistoryAware();
-    method public boolean navigateBack(optional String backNavigationBehavior);
-    method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
+    method public suspend Object? navigateBack(optional String backNavigationBehavior, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
+    method public suspend Object? seekBack(optional String backNavigationBehavior, optional @FloatRange(from=0.0, to=1.0) float fraction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setDestinationHistoryAware(boolean);
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
     property public abstract boolean isDestinationHistoryAware;
     property public abstract androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue scaffoldValue;
   }
 
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index 4612aff..6acaa74 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -25,15 +25,18 @@
     method public boolean canNavigateBack(optional String backNavigationBehavior);
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? getCurrentDestination();
     method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
     method public boolean isDestinationHistoryAware();
-    method public boolean navigateBack(optional String backNavigationBehavior);
-    method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
+    method public suspend Object? navigateBack(optional String backNavigationBehavior, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
+    method public suspend Object? seekBack(optional String backNavigationBehavior, optional @FloatRange(from=0.0, to=1.0) float fraction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setDestinationHistoryAware(boolean);
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
     property public abstract boolean isDestinationHistoryAware;
     property public abstract androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
     property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue scaffoldValue;
   }
 
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
index 5296dfd..28068dd 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -23,12 +23,16 @@
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.properties.Delegates
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,10 +45,12 @@
 
     @Test
     fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockSinglePaneScaffoldDirective
@@ -52,7 +58,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Hidden)
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
@@ -70,10 +76,12 @@
 
     @Test
     fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockDualPaneScaffoldDirective
@@ -81,7 +89,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
@@ -99,10 +107,12 @@
 
     @Test
     fun dualPaneLayout_navigateToExtra_hideListWhenNotHistoryAware() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -111,7 +121,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
@@ -129,10 +139,12 @@
 
     @Test
     fun dualPaneLayout_navigateToExtra_keepListExpandedWhenHistoryAware() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -141,7 +153,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra, 0)
@@ -159,10 +171,12 @@
 
     @Test
     fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockSinglePaneScaffoldDirective
@@ -170,9 +184,11 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle { scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0) }
+        scope.runBlockingOnIdle {
+            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
+        }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -218,9 +234,11 @@
 
     @Test
     fun dualPaneLayout_withSimplePop_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -232,7 +250,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -253,9 +271,11 @@
 
     @Test
     fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -268,7 +288,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(ListDetailPaneScaffoldRole.Detail)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
@@ -319,9 +339,11 @@
 
     @Test
     fun dualPaneLayout_enforceContentChange_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -334,7 +356,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(ListDetailPaneScaffoldRole.Detail)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
@@ -354,9 +376,11 @@
 
     @Test
     fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -369,7 +393,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(ListDetailPaneScaffoldRole.Extra)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
@@ -416,9 +440,11 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -431,7 +457,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Hidden,
                 PaneAdaptedValue.Expanded,
@@ -443,7 +469,7 @@
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -469,9 +495,11 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -484,7 +512,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -496,7 +524,7 @@
             scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -522,9 +550,11 @@
 
     @Test
     fun singlePaneLayout_previousScaffoldValue_popLatest() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockSinglePaneScaffoldDirective,
@@ -536,7 +566,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
@@ -581,9 +611,11 @@
 
     @Test
     fun singlePaneLayout_previousScaffoldValue_popUntilScaffoldValueChange() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockSinglePaneScaffoldDirective,
@@ -596,7 +628,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.scaffoldValue[ListDetailPaneScaffoldRole.List])
@@ -676,6 +708,11 @@
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
         }
     }
+
+    private fun CoroutineScope.runBlockingOnIdle(block: suspend CoroutineScope.() -> Unit) {
+        val job = composeRule.runOnIdle { launch(block = block) }
+        runBlocking { job.join() }
+    }
 }
 
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective.Default
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
index ef9c1bf..7e15c38 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -23,12 +23,16 @@
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.properties.Delegates
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,10 +45,12 @@
 
     @Test
     fun singlePaneLayout_navigateTo_makeDestinationPaneExpanded() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockSinglePaneScaffoldDirective
@@ -52,7 +58,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Hidden)
             scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
@@ -70,10 +76,12 @@
 
     @Test
     fun dualPaneLayout_navigateTo_keepDestinationPaneExpanded() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockDualPaneScaffoldDirective
@@ -81,7 +89,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
@@ -99,10 +107,12 @@
 
     @Test
     fun dualPaneLayout_navigateToExtra_hideSupportingWhenNotHistoryAware() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     initialDestinationHistory =
@@ -118,7 +128,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -139,10 +149,12 @@
 
     @Test
     fun dualPaneLayout_navigateToExtra_keepSupportingExpandedWhenHistoryAware() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     initialDestinationHistory =
@@ -158,7 +170,7 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -179,10 +191,12 @@
 
     @Test
     fun singlePaneLayout_navigateBack_makeDestinationPaneHidden() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator<Int>(
                     scaffoldDirective = MockSinglePaneScaffoldDirective
@@ -190,11 +204,11 @@
             canNavigateBack = scaffoldNavigator.canNavigateBack()
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting, 0)
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -243,9 +257,11 @@
 
     @Test
     fun dualPaneLayout_withSimplePop_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -260,7 +276,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.currentDestination?.pane)
@@ -281,9 +297,11 @@
 
     @Test
     fun dualPaneLayout_enforceCurrentDestinationChange_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -302,7 +320,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(SupportingPaneScaffoldRole.Supporting)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
@@ -353,9 +371,11 @@
 
     @Test
     fun dualPaneLayout_enforceContentChange_canNavigateBack() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -374,7 +394,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(SupportingPaneScaffoldRole.Supporting)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
@@ -394,9 +414,11 @@
 
     @Test
     fun dualPaneLayout_enforceContentChange_canNavigateBack_withOnlyScaffoldValueChange() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -412,7 +434,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.currentDestination?.pane)
                 .isEqualTo(SupportingPaneScaffoldRole.Extra)
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
@@ -462,9 +484,11 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldChangeWhenHistoryAware_notSkipBackstackEntry() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -480,7 +504,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Hidden,
                 PaneAdaptedValue.Expanded,
@@ -492,7 +516,7 @@
             scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -518,9 +542,11 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldChangeWhenNotHistoryAware_skipBackstackEntry() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberSupportingPaneScaffoldNavigator(
                     scaffoldDirective = MockDualPaneScaffoldDirective,
@@ -536,7 +562,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -548,7 +574,7 @@
             scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             scaffoldNavigator.scaffoldValue.assert(
                 PaneAdaptedValue.Expanded,
                 PaneAdaptedValue.Expanded,
@@ -574,9 +600,11 @@
 
     @Test
     fun singlePaneLayout_previousScaffoldValue_popLatest() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockSinglePaneScaffoldDirective,
@@ -591,7 +619,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
@@ -636,9 +664,11 @@
 
     @Test
     fun singlePaneLayout_previousScaffoldValue_popUntilScaffoldValueChange() {
+        lateinit var scope: CoroutineScope
         lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<Int>
 
         composeRule.setContent {
+            scope = rememberCoroutineScope()
             scaffoldNavigator =
                 rememberListDetailPaneScaffoldNavigator(
                     scaffoldDirective = MockSinglePaneScaffoldDirective,
@@ -657,7 +687,7 @@
                 )
         }
 
-        composeRule.runOnIdle {
+        scope.runBlockingOnIdle {
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting])
                 .isEqualTo(PaneAdaptedValue.Expanded)
             assertThat(scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Main])
@@ -740,6 +770,11 @@
             assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
         }
     }
+
+    private fun CoroutineScope.runBlockingOnIdle(block: suspend CoroutineScope.() -> Unit) {
+        val job = composeRule.runOnIdle { launch(block = block) }
+        runBlocking { job.join() }
+    }
 }
 
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective.Default
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
index 859c8b8..9b802fc8 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
@@ -16,24 +16,30 @@
 
 package androidx.compose.material3.adaptive.navigation
 
-import androidx.activity.compose.BackHandler
+import androidx.compose.animation.core.Animatable
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold as BaseListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
 import androidx.compose.material3.adaptive.layout.PaneExpansionState
-import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold as BaseSupportingPaneScaffold
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold
+import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.ThreePaneMotion
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
 import androidx.compose.material3.adaptive.layout.calculateListDetailPaneScaffoldMotion
 import androidx.compose.material3.adaptive.layout.calculateSupportingPaneScaffoldMotion
 import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
 
 /**
- * A version of [androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold] that supports
- * navigation and back handling out of the box, controlled by [ThreePaneScaffoldNavigator].
+ * A version of [ListDetailPaneScaffold] that supports navigation and predictive back handling out
+ * of the box, controlled by [ThreePaneScaffoldNavigator].
  *
  * @param navigator The navigator instance to navigate through the scaffold.
  * @param listPane the list pane of the scaffold, which is supposed to hold a list of item summaries
@@ -67,19 +73,23 @@
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
     defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
-    paneMotions: ThreePaneMotion = calculateListDetailPaneScaffoldMotion(navigator.scaffoldValue),
+    paneMotions: ThreePaneMotion = navigator.scaffoldState.calculateListDetailPaneScaffoldMotion(),
     paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
         null,
     paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
-    // TODO(b/330584029): support predictive back
-    BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
-        navigator.navigateBack(defaultBackBehavior)
-    }
-    BaseListDetailPaneScaffold(
-        modifier = modifier,
+    val predictiveBackScale = remember { Animatable(initialValue = 1f) }
+
+    ThreePaneScaffoldPredictiveBackHandler(
+        navigator = navigator,
+        backBehavior = defaultBackBehavior,
+        scale = predictiveBackScale,
+    )
+
+    ListDetailPaneScaffold(
+        modifier = modifier.predictiveBackTransform(predictiveBackScale::value),
         directive = navigator.scaffoldDirective,
-        value = navigator.scaffoldValue,
+        scaffoldState = navigator.scaffoldState,
         detailPane = detailPane,
         listPane = listPane,
         extraPane = extraPane,
@@ -90,8 +100,8 @@
 }
 
 /**
- * A version of [androidx.compose.material3.adaptive.layout.SupportingPaneScaffold] that supports
- * navigation and back handling out of the box, controlled by [ThreePaneScaffoldNavigator].
+ * A version of [SupportingPaneScaffold] that supports navigation and predictive back handling out
+ * of the box, controlled by [ThreePaneScaffoldNavigator].
  *
  * @param navigator The navigator instance to navigate through the scaffold.
  * @param mainPane the main pane of the scaffold, which is supposed to hold the major content of an
@@ -124,19 +134,23 @@
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
     defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
-    paneMotions: ThreePaneMotion = calculateSupportingPaneScaffoldMotion(navigator.scaffoldValue),
+    paneMotions: ThreePaneMotion = navigator.scaffoldState.calculateSupportingPaneScaffoldMotion(),
     paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
         null,
     paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
-    // TODO(b/330584029): support predictive back
-    BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
-        navigator.navigateBack(defaultBackBehavior)
-    }
-    BaseSupportingPaneScaffold(
-        modifier = modifier,
+    val predictiveBackScale = remember { Animatable(initialValue = 1f) }
+
+    ThreePaneScaffoldPredictiveBackHandler(
+        navigator = navigator,
+        backBehavior = defaultBackBehavior,
+        scale = predictiveBackScale,
+    )
+
+    SupportingPaneScaffold(
+        modifier = modifier.predictiveBackTransform(predictiveBackScale::value),
         directive = navigator.scaffoldDirective,
-        value = navigator.scaffoldValue,
+        scaffoldState = navigator.scaffoldState,
         mainPane = mainPane,
         supportingPane = supportingPane,
         extraPane = extraPane,
@@ -145,3 +159,12 @@
         paneExpansionState = paneExpansionState,
     )
 }
+
+private fun Modifier.predictiveBackTransform(scale: () -> Float): Modifier = graphicsLayer {
+    val scaleValue = scale()
+    scaleX = scaleValue
+    scaleY = scaleValue
+    transformOrigin = TransformOriginTopCenter
+}
+
+private val TransformOriginTopCenter = TransformOrigin(pivotFractionX = 0.5f, pivotFractionY = 0f)
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldPredictiveBackHandler.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldPredictiveBackHandler.android.kt
new file mode 100644
index 0000000..afaaf59
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldPredictiveBackHandler.android.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.adaptive.navigation
+
+import androidx.activity.compose.PredictiveBackHandler
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.Easing
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.key
+import androidx.compose.ui.util.lerp
+import kotlin.coroutines.cancellation.CancellationException
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+internal fun ThreePaneScaffoldPredictiveBackHandler(
+    navigator: ThreePaneScaffoldNavigator<Any>,
+    backBehavior: BackNavigationBehavior,
+    scale: Animatable<Float, AnimationVector1D>,
+) {
+    fun backProgressToAnimationProgress(value: Float): Float =
+        PredictiveBackDefaults.Easing.transform(value) *
+            when (navigator.scaffoldValue.expandedCount) {
+                1 -> PredictiveBackDefaults.SinglePaneProgressRatio
+                2 -> PredictiveBackDefaults.DualPaneProgressRatio
+                else -> PredictiveBackDefaults.TriplePaneProgressRatio
+            }
+    fun backProgressToScale(value: Float): Float =
+        lerp(1f, PredictiveBackDefaults.MinScale, PredictiveBackDefaults.Easing.transform(value))
+
+    key(navigator, backBehavior) {
+        PredictiveBackHandler(enabled = navigator.canNavigateBack(backBehavior)) { progress ->
+            // code for gesture back started
+            try {
+                progress.collect { backEvent ->
+                    scale.snapTo(backProgressToScale(backEvent.progress))
+                    navigator.seekBack(
+                        backBehavior,
+                        fraction = backProgressToAnimationProgress(backEvent.progress),
+                    )
+                }
+                // code for completion
+                scale.animateTo(1f)
+                navigator.navigateBack(backBehavior)
+            } catch (e: CancellationException) {
+                // code for cancellation
+                scale.animateTo(1f)
+                navigator.seekBack(backBehavior, fraction = 0f)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val ThreePaneScaffoldValue.expandedCount: Int
+    get() {
+        var count = 0
+        if (primary == PaneAdaptedValue.Expanded) {
+            count++
+        }
+        if (secondary == PaneAdaptedValue.Expanded) {
+            count++
+        }
+        if (tertiary == PaneAdaptedValue.Expanded) {
+            count++
+        }
+        return count
+    }
+
+private object PredictiveBackDefaults {
+    val Easing: Easing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f)
+    const val MinScale: Float = 0.95f
+    const val SinglePaneProgressRatio: Float = 0.1f
+    const val DualPaneProgressRatio: Float = 0.15f
+    const val TriplePaneProgressRatio: Float = 0.2f
+}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
index 42378e4..7ea6353 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
@@ -16,11 +16,13 @@
 
 package androidx.compose.material3.adaptive.navigation
 
+import androidx.annotation.FloatRange
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.MutableThreePaneScaffoldState
 import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
 import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold
 import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults
@@ -28,6 +30,7 @@
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
 import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
 import androidx.compose.material3.adaptive.layout.calculateThreePaneScaffoldValue
@@ -73,7 +76,13 @@
     val scaffoldDirective: PaneScaffoldDirective
 
     /**
-     * The current layout value of the associated three pane scaffold value, which represents unique
+     * The current state of the associated three pane scaffold, used to query the transition between
+     * layout states.
+     */
+    val scaffoldState: ThreePaneScaffoldState
+
+    /**
+     * The current layout value of the associated three pane scaffold, which represents unique
      * layout states of the scaffold.
      */
     val scaffoldValue: ThreePaneScaffoldValue
@@ -118,7 +127,7 @@
      * @param pane the new destination pane.
      * @param contentKey the optional key or id representing the content of the new destination.
      */
-    fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T? = null)
+    suspend fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T? = null)
 
     /**
      * Returns `true` if there is a previous destination to navigate back to.
@@ -144,10 +153,27 @@
      * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
      *   during the back navigation. See [BackNavigationBehavior].
      */
-    fun navigateBack(
+    suspend fun navigateBack(
         backNavigationBehavior: BackNavigationBehavior =
             BackNavigationBehavior.PopUntilScaffoldValueChange
     ): Boolean
+
+    /**
+     * Seeks the [scaffoldState] transition to the previous destination, as in a predictive back
+     * animation.
+     *
+     * This does not affect the current [scaffoldValue] or backstack. To do so, call [navigateBack]
+     * when the back navigation action is finalized.
+     *
+     * @param backNavigationBehavior the behavior describing which backstack entries may be skipped
+     *   during the back navigation. See [BackNavigationBehavior].
+     * @param fraction the progress fraction of the transition of backwards navigation.
+     */
+    suspend fun seekBack(
+        backNavigationBehavior: BackNavigationBehavior =
+            BackNavigationBehavior.PopUntilScaffoldValueChange,
+        @FloatRange(from = 0.0, to = 1.0) fraction: Float = 1.0f,
+    )
 }
 
 /**
@@ -341,6 +367,9 @@
         calculateScaffoldValue(destinationHistory.lastIndex)
     }
 
+    // Must be updated whenever `destinationHistory` changes to keep in sync.
+    override val scaffoldState = MutableThreePaneScaffoldState(scaffoldValue)
+
     override fun peekPreviousScaffoldValue(
         backNavigationBehavior: BackNavigationBehavior
     ): ThreePaneScaffoldValue {
@@ -348,26 +377,40 @@
         return if (index == -1) scaffoldValue else calculateScaffoldValue(index)
     }
 
-    override fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T?) {
+    override suspend fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T?) {
         destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, contentKey))
+        animateStateToCurrentScaffoldValue()
     }
 
     override fun canNavigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean =
         getPreviousDestinationIndex(backNavigationBehavior) >= 0
 
-    override fun navigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean {
+    override suspend fun navigateBack(
+        backNavigationBehavior: BackNavigationBehavior,
+    ): Boolean {
         val previousDestinationIndex = getPreviousDestinationIndex(backNavigationBehavior)
         if (previousDestinationIndex < 0) {
             destinationHistory.clear()
+            animateStateToCurrentScaffoldValue()
             return false
         }
         val targetSize = previousDestinationIndex + 1
         while (destinationHistory.size > targetSize) {
             destinationHistory.removeLastKt()
         }
+        animateStateToCurrentScaffoldValue()
         return true
     }
 
+    override suspend fun seekBack(backNavigationBehavior: BackNavigationBehavior, fraction: Float) {
+        val previousScaffoldValue = peekPreviousScaffoldValue(backNavigationBehavior)
+        scaffoldState.seekTo(fraction, previousScaffoldValue)
+    }
+
+    private suspend fun animateStateToCurrentScaffoldValue() {
+        scaffoldState.animateTo(scaffoldValue)
+    }
+
     private fun getPreviousDestinationIndex(backNavBehavior: BackNavigationBehavior): Int {
         if (destinationHistory.size <= 1) {
             // No previous destination
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
index d30081c..9e242d3 100644
--- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -61,6 +61,7 @@
 import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
 import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
@@ -68,6 +69,7 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Preview
@@ -75,6 +77,7 @@
 @Composable
 fun ListDetailPaneScaffoldSample() {
     val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator()
+    val coroutineScope = rememberCoroutineScope()
     ListDetailPaneScaffold(
         directive = scaffoldNavigator.scaffoldDirective,
         value = scaffoldNavigator.scaffoldValue,
@@ -84,7 +87,11 @@
             ) {
                 Surface(
                     color = MaterialTheme.colorScheme.secondary,
-                    onClick = { scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) }
+                    onClick = {
+                        coroutineScope.launch {
+                            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
+                        }
+                    }
                 ) {
                     Text("List")
                 }
@@ -94,7 +101,7 @@
             AnimatedPane(modifier = Modifier) {
                 Surface(
                     color = MaterialTheme.colorScheme.primary,
-                    onClick = { scaffoldNavigator.navigateBack() }
+                    onClick = { coroutineScope.launch { scaffoldNavigator.navigateBack() } }
                 ) {
                     Text("Details")
                 }
@@ -109,6 +116,7 @@
 @Composable
 fun ListDetailPaneScaffoldSampleWithExtraPane() {
     val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator()
+    val coroutineScope = rememberCoroutineScope()
     ListDetailPaneScaffold(
         directive = scaffoldNavigator.scaffoldDirective,
         value = scaffoldNavigator.scaffoldValue,
@@ -118,7 +126,11 @@
             ) {
                 Surface(
                     color = MaterialTheme.colorScheme.secondary,
-                    onClick = { scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) }
+                    onClick = {
+                        coroutineScope.launch {
+                            scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
+                        }
+                    }
                 ) {
                     Text("List")
                 }
@@ -136,7 +148,9 @@
                             horizontalArrangement = Arrangement.spacedBy(8.dp)
                         ) {
                             Surface(
-                                onClick = { scaffoldNavigator.navigateBack() },
+                                onClick = {
+                                    coroutineScope.launch { scaffoldNavigator.navigateBack() }
+                                },
                                 modifier = Modifier.weight(0.5f).fillMaxHeight(),
                                 color = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f)
                             ) {
@@ -150,7 +164,11 @@
                             VerticalDivider()
                             Surface(
                                 onClick = {
-                                    scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Extra)
+                                    coroutineScope.launch {
+                                        scaffoldNavigator.navigateTo(
+                                            ListDetailPaneScaffoldRole.Extra
+                                        )
+                                    }
                                 },
                                 modifier = Modifier.weight(0.5f).fillMaxHeight(),
                                 color = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
@@ -172,7 +190,7 @@
                 Surface(
                     modifier = Modifier.fillMaxSize(),
                     color = MaterialTheme.colorScheme.tertiary,
-                    onClick = { scaffoldNavigator.navigateBack() }
+                    onClick = { coroutineScope.launch { scaffoldNavigator.navigateBack() } }
                 ) {
                     Text("Extra")
                 }
@@ -202,9 +220,9 @@
     val listDetailRoute = "listdetail"
     val items = List(15) { "Item $it" }
     val loremIpsum =
-        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " +
-            "tempor incididunt ut labore et dolore magna aliqua. Dui nunc mattis enim ut tellus " +
-            "elementum sagittis. Nunc sed augue lacus viverra vitae. Sit amet dictum sit amet justo " +
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " +
+            "incididunt ut labore et dolore magna aliqua. Dui nunc mattis enim ut tellus " +
+            "elementum sagittis. Nunc sed augue lacus viverra vitae. Sit amet dictum sit amet " +
             "donec. Fringilla urna porttitor rhoncus dolor purus non enim praesent elementum."
 
     @Composable
@@ -262,6 +280,7 @@
     // scaffold navigator to be aware of its content, you can pass `Nothing`.
     val navController = rememberNavController()
     val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<String>()
+    val coroutineScope = rememberCoroutineScope()
 
     NavHost(
         navController = navController,
@@ -304,7 +323,7 @@
                 }
 
             BackHandler(enabled = scaffoldNavigator.canNavigateBack(backBehavior)) {
-                scaffoldNavigator.navigateBack(backBehavior)
+                coroutineScope.launch { scaffoldNavigator.navigateBack(backBehavior) }
             }
 
             ListDetailPaneScaffold(
@@ -326,10 +345,13 @@
                                         modifier =
                                             Modifier.clickable {
                                                 if (item != selectedItem) {
-                                                    scaffoldNavigator.navigateTo(
-                                                        pane = ListDetailPaneScaffoldRole.Detail,
-                                                        contentKey = item,
-                                                    )
+                                                    coroutineScope.launch {
+                                                        scaffoldNavigator.navigateTo(
+                                                            pane =
+                                                                ListDetailPaneScaffoldRole.Detail,
+                                                            contentKey = item,
+                                                        )
+                                                    }
                                                 }
                                             }
                                     )
@@ -357,7 +379,9 @@
                                     ) {
                                         IconButton(
                                             onClick = {
-                                                scaffoldNavigator.navigateBack(backBehavior)
+                                                coroutineScope.launch {
+                                                    scaffoldNavigator.navigateBack(backBehavior)
+                                                }
                                             },
                                             content = {
                                                 Icon(Icons.AutoMirrored.Filled.ArrowBack, null)
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2fb36a5..a67bce1 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1419,17 +1419,12 @@
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> fastSpatialSpec();
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> slowEffectsSpec();
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> slowSpatialSpec();
+    field public static final androidx.compose.material3.MotionScheme.Companion Companion;
   }
 
-  public final class MotionSchemeKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.MotionScheme expressiveMotionScheme();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberDefaultEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberDefaultSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberFastEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberFastSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberSlowEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberSlowSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.MotionScheme standardMotionScheme();
+  public static final class MotionScheme.Companion {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.MotionScheme expressiveMotionScheme();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.MotionScheme standardMotionScheme();
   }
 
   public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -1972,12 +1967,13 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class ShortNavigationBarItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors(optional long selectedIconColor, optional long selectedTextColor, optional long selectedIndicatorColor, optional long unselectedIconColor, optional long unselectedTextColor, optional long disabledIconColor, optional long disabledTextColor);
     field public static final androidx.compose.material3.ShortNavigationBarItemDefaults INSTANCE;
   }
 
   public final class ShortNavigationBarKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -2753,7 +2749,8 @@
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TopAppBarDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec, optional boolean reverseLayout);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method public float getLargeAppBarCollapsedHeight();
     method public float getLargeAppBarExpandedHeight();
@@ -2975,6 +2972,7 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WideNavigationRailDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.WideNavigationRailColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.WideNavigationRailColors colors(optional long containerColor, optional long contentColor, optional long modalContainerColor, optional long modalScrimColor);
     method public int getArrangement();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getContainerShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getModalContainerShape();
@@ -2988,6 +2986,7 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WideNavigationRailItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors(optional long selectedIconColor, optional long selectedTextColor, optional long selectedIndicatorColor, optional long unselectedIconColor, optional long unselectedTextColor, optional long disabledIconColor, optional long disabledTextColor);
     method public int iconPositionFor(boolean railExpanded);
     field public static final androidx.compose.material3.WideNavigationRailItemDefaults INSTANCE;
   }
@@ -2996,7 +2995,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void DismissibleModalWideNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DismissibleModalWideNavigationRailState railState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional boolean gesturesEnabled, optional androidx.compose.material3.ModalWideNavigationRailProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ModalWideNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> scrimOnClick, optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape collapsedShape, optional androidx.compose.ui.graphics.Shape expandedShape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional float expandedHeaderTopPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional androidx.compose.material3.ModalWideNavigationRailProperties expandedProperties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRail(optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class WideNavigationRailStateKt {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2fb36a5..a67bce1 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1419,17 +1419,12 @@
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> fastSpatialSpec();
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> slowEffectsSpec();
     method public <T> androidx.compose.animation.core.FiniteAnimationSpec<T> slowSpatialSpec();
+    field public static final androidx.compose.material3.MotionScheme.Companion Companion;
   }
 
-  public final class MotionSchemeKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.MotionScheme expressiveMotionScheme();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberDefaultEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberDefaultSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberFastEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberFastSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberSlowEffectsSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static inline <reified T> androidx.compose.animation.core.FiniteAnimationSpec<T> rememberSlowSpatialSpec(androidx.compose.material3.MotionScheme);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.MotionScheme standardMotionScheme();
+  public static final class MotionScheme.Companion {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.MotionScheme expressiveMotionScheme();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.MotionScheme standardMotionScheme();
   }
 
   public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -1972,12 +1967,13 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class ShortNavigationBarItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors(optional long selectedIconColor, optional long selectedTextColor, optional long selectedIndicatorColor, optional long unselectedIconColor, optional long unselectedTextColor, optional long disabledIconColor, optional long disabledTextColor);
     field public static final androidx.compose.material3.ShortNavigationBarItemDefaults INSTANCE;
   }
 
   public final class ShortNavigationBarKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ShortNavigationBarItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
@@ -2753,7 +2749,8 @@
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TopAppBarDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec, optional boolean reverseLayout);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method public float getLargeAppBarCollapsedHeight();
     method public float getLargeAppBarExpandedHeight();
@@ -2975,6 +2972,7 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WideNavigationRailDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.WideNavigationRailColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.WideNavigationRailColors colors(optional long containerColor, optional long contentColor, optional long modalContainerColor, optional long modalScrimColor);
     method public int getArrangement();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getContainerShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getModalContainerShape();
@@ -2988,6 +2986,7 @@
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class WideNavigationRailItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationItemColors colors(optional long selectedIconColor, optional long selectedTextColor, optional long selectedIndicatorColor, optional long unselectedIconColor, optional long unselectedTextColor, optional long disabledIconColor, optional long disabledTextColor);
     method public int iconPositionFor(boolean railExpanded);
     field public static final androidx.compose.material3.WideNavigationRailItemDefaults INSTANCE;
   }
@@ -2996,7 +2995,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void DismissibleModalWideNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DismissibleModalWideNavigationRailState railState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional boolean gesturesEnabled, optional androidx.compose.material3.ModalWideNavigationRailProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ModalWideNavigationRail(kotlin.jvm.functions.Function0<kotlin.Unit> scrimOnClick, optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape collapsedShape, optional androidx.compose.ui.graphics.Shape expandedShape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional float expandedHeaderTopPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, optional androidx.compose.material3.ModalWideNavigationRailProperties expandedProperties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRail(optional androidx.compose.ui.Modifier modifier, optional boolean expanded, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.WideNavigationRailColors colors, optional kotlin.jvm.functions.Function0<kotlin.Unit>? header, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional int arrangement, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void WideNavigationRailItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean railExpanded, optional int iconPosition, optional androidx.compose.material3.NavigationItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   public final class WideNavigationRailStateKt {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
index f46d70c..a9d6feb 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
@@ -18,11 +18,11 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.Icon
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.TrendingUp
 import androidx.compose.material.icons.filled.BookmarkBorder
 import androidx.compose.material.icons.filled.StarBorder
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MultiChoiceSegmentedButtonRow
 import androidx.compose.material3.SegmentedButton
 import androidx.compose.material3.SegmentedButtonDefaults
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
index d67790d..5089341 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -21,9 +21,13 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
@@ -63,13 +67,16 @@
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onChildAt
 import androidx.compose.ui.test.onFirst
 import androidx.compose.ui.test.onLast
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
@@ -1438,6 +1445,131 @@
         assertThat(TopAppBarSmallTokens.ContainerHeight).isLessThan(boundsAfter.height)
     }
 
+    // Disabled on older APIs which seem to run on a small Nexus device that fails this test.
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    @Test
+    fun topAppBar_enterAlways_scrollingAndContentMovement() {
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        lateinit var state: LazyListState
+        var appBarHeightPx = 0f
+        rule.setMaterialContentForSizeAssertions {
+            scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+            state = rememberLazyListState()
+            appBarHeightPx = with(rule.density) { TopAppBarSmallTokens.ContainerHeight.toPx() }
+            Scaffold(
+                modifier = Modifier.fillMaxSize().consumeWindowInsets(WindowInsets.systemBars),
+                topBar = { TopAppBar(title = { Text("Title") }, scrollBehavior = scrollBehavior) },
+            ) { paddingValues ->
+                LazyColumn(
+                    modifier =
+                        Modifier.fillMaxSize()
+                            .nestedScroll(scrollBehavior.nestedScrollConnection)
+                            .padding(paddingValues)
+                            .testTag(LazyListTag),
+                    state = state,
+                ) {
+                    items(100) { i ->
+                        Text(
+                            modifier =
+                                Modifier.fillMaxWidth().height(40.dp).padding(horizontal = 16.dp),
+                            text = "Item $i",
+                        )
+                    }
+                }
+            }
+        }
+
+        // Swipe up to scroll the content and collapse the top app bar.
+        rule.onNodeWithTag(LazyListTag).performTouchInput {
+            swipeUp(startY = height - 200f, endY = height - 1000f)
+        }
+        rule.waitForIdle()
+
+        // Store the first visible item's top offset.
+        val topVisibleItemIndex = state.layoutInfo.visibleItemsInfo.first().index
+        val topItemTopBeforeExpansion =
+            rule.onNodeWithTag(LazyListTag).onChildAt(topVisibleItemIndex).getBoundsInRoot().top
+
+        // Swipe down to trigger a top app bar expansion without scrolling much the content.
+        rule.onNodeWithTag(LazyListTag).performTouchInput {
+            swipeDown(startY = height - 1000f, endY = height - (1000f - appBarHeightPx / 2))
+        }
+        rule.waitForIdle()
+
+        // Asserts that the first item has moved along with the expansion of the top app bar.
+        rule
+            .onNodeWithTag(LazyListTag)
+            .onChildAt(topVisibleItemIndex)
+            .assertTopPositionInRootIsEqualTo(
+                topItemTopBeforeExpansion + TopAppBarSmallTokens.ContainerHeight
+            )
+    }
+
+    @Test
+    fun topAppBar_enterAlways_reverseLayout_scrollingAndContentMovement() {
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        lateinit var state: LazyListState
+        var appBarHeightPx = 0f
+
+        rule.setMaterialContentForSizeAssertions {
+            scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(reverseLayout = true)
+            state = rememberLazyListState()
+            appBarHeightPx = with(rule.density) { TopAppBarSmallTokens.ContainerHeight.toPx() }
+            Scaffold(
+                modifier = Modifier.fillMaxSize().consumeWindowInsets(WindowInsets.systemBars),
+                topBar = {
+                    TopAppBar(
+                        title = { Text("Title") },
+                        modifier = Modifier.testTag(TopAppBarTestTag),
+                        scrollBehavior = scrollBehavior
+                    )
+                },
+            ) { paddingValues ->
+                LazyColumn(
+                    modifier =
+                        Modifier.fillMaxSize()
+                            .nestedScroll(scrollBehavior.nestedScrollConnection)
+                            .padding(paddingValues)
+                            .testTag(LazyListTag),
+                    state = state,
+                    reverseLayout = true,
+                ) {
+                    items(100) { i ->
+                        Text(
+                            modifier =
+                                Modifier.fillMaxWidth().height(48.dp).padding(horizontal = 16.dp),
+                            text = "Item $i",
+                        )
+                    }
+                }
+            }
+        }
+
+        // Swipe down to scroll the content in the reverse layout.
+        rule.onNodeWithTag(LazyListTag).performTouchInput {
+            swipeDown(startY = height - 1000f, endY = height - 300f)
+        }
+        rule.waitForIdle()
+
+        // Swipe up to trigger a top app bar collapse without scrolling much the content.
+        rule.onNodeWithTag(LazyListTag).performTouchInput {
+            down(Offset(x = width / 2f, y = height / 2f))
+            moveTo(Offset(x = width / 2f, y = height / 2f - appBarHeightPx + 50))
+        }
+        rule.waitForIdle()
+
+        // Asserts that the first item has moved along with the collapsing of the top app bar.
+        val newTopVisibleItemIndex = state.layoutInfo.visibleItemsInfo.last().index
+        val bottomAppBarWhileCollapsing =
+            rule.onNodeWithTag(TopAppBarTestTag).getBoundsInRoot().bottom
+        val topVisibleItemTopWhileCollapsing =
+            rule.onNodeWithText("Item $newTopVisibleItemIndex").getBoundsInRoot().top
+        topVisibleItemTopWhileCollapsing.assertIsEqualTo(
+            expected = bottomAppBarWhileCollapsing,
+            subject = "Top item comparison to bottom app bar"
+        )
+    }
+
     @Test
     fun state_restoresTopAppBarState() {
         val restorationTester = StateRestorationTester(rule)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index 3e166ef..40418e9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -80,6 +80,7 @@
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
@@ -1026,4 +1027,62 @@
             }
         }
     }
+
+    @Test
+    fun bottomSheetScaffold_testDragHandleClick() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberStandardBottomSheetState()
+            BottomSheetScaffold(
+                sheetContent = {
+                    Box(Modifier.fillMaxWidth().requiredHeight(sheetHeight).testTag(sheetTag))
+                },
+                sheetDragHandle = { Box(Modifier.testTag(dragHandleTag).size(dragHandleSize)) },
+                sheetPeekHeight = peekHeight,
+                scaffoldState = rememberBottomSheetScaffoldState(bottomSheetState = sheetState)
+            ) {
+                Text("Content")
+            }
+        }
+
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded)
+    }
+
+    @Test
+    fun bottomSheetScaffold_testDragHandleClick_hiddenStateAllowed() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberStandardBottomSheetState(skipHiddenState = false)
+            BottomSheetScaffold(
+                sheetContent = {
+                    Box(Modifier.fillMaxWidth().requiredHeight(sheetHeight).testTag(sheetTag))
+                },
+                sheetDragHandle = { Box(Modifier.testTag(dragHandleTag).size(dragHandleSize)) },
+                sheetPeekHeight = peekHeight,
+                scaffoldState = rememberBottomSheetScaffoldState(bottomSheetState = sheetState)
+            ) {
+                Text("Content")
+            }
+        }
+
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+    }
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
index c0565c4..9f1854f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.testutils.assertAgainstGolden
@@ -32,6 +33,9 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.MockedMethod
 import java.time.LocalDate
 import java.time.LocalTime
 import java.time.ZoneId
@@ -39,6 +43,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
+import org.mockito.quality.Strictness
 
 @RunWith(Parameterized::class)
 @LargeTest
@@ -68,6 +73,76 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun datePicker_todayMarker() {
+        val year = 2021
+        val month = 1
+        val day = 10
+        runWithMockedLocalDate(mockedToday = LocalDate.of(year, month, day)) {
+            rule.setMaterialContent(scheme.colorScheme) {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    val monthInUtcMillis =
+                        dayInUtcMilliseconds(year = year, month = month, dayOfMonth = day)
+                    DatePicker(
+                        state =
+                            rememberDatePickerState(initialDisplayedMonthMillis = monthInUtcMillis),
+                        showModeToggle = false
+                    )
+                }
+            }
+            assertAgainstGolden("datePicker_todayMarker_${scheme.name}")
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun datePicker_disabledTodayMarker() {
+        val year = 2021
+        val month = 1
+        val day = 10
+        runWithMockedLocalDate(mockedToday = LocalDate.of(year, month, day)) {
+            rule.setMaterialContent(scheme.colorScheme) {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    val monthInUtcMillis =
+                        dayInUtcMilliseconds(year = year, month = month, dayOfMonth = day)
+                    DatePicker(
+                        state =
+                            rememberDatePickerState(
+                                initialDisplayedMonthMillis = monthInUtcMillis,
+                                selectableDates =
+                                    object : SelectableDates {
+                                        override fun isSelectableDate(
+                                            utcTimeMillis: Long
+                                        ): Boolean = false
+                                    }
+                            ),
+                        showModeToggle = false
+                    )
+                }
+            }
+            assertAgainstGolden("datePicker_disabledTodayMarker_${scheme.name}")
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    private fun runWithMockedLocalDate(mockedToday: LocalDate, test: () -> Unit) {
+        val session =
+            mockitoSession()
+                .spyStatic(LocalDate::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        try {
+            // Mock `LocalDate.now()` to return a specific date. This will mark the today marker
+            // on the month displayed in this test.
+            doReturn(mockedToday).`when`(MockedMethod { LocalDate.now() })
+            // Run the test
+            test()
+        } finally {
+            session.finishMocking()
+        }
+    }
+
+    @Test
     fun datePicker_withModeToggle() {
         rule.setMaterialContent(scheme.colorScheme) {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 074b5a7..9b34712 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -780,8 +780,8 @@
                 backgroundColor = Color.Red,
                 backgroundSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
                 shapeSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
-                shapeAndBackgroundCenter =
-                    with(rule.density) { Offset(70.dp.toPx(), 70.dp.toPx()) },
+                shapeCenter = with(rule.density) { Offset(70.dp.toPx(), 70.dp.toPx()) },
+                backgroundCenter = with(rule.density) { Offset(70.dp.toPx(), 70.dp.toPx()) },
                 antiAliasingGap = with(rule.density) { 3.dp.toPx() }
             )
     }
@@ -840,8 +840,8 @@
                 backgroundColor = Color.Red,
                 backgroundSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
                 shapeSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
-                shapeAndBackgroundCenter =
-                    with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                shapeCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                backgroundCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
                 antiAliasingGap = with(rule.density) { 2.dp.toPx() }
             )
     }
@@ -900,8 +900,8 @@
                 backgroundColor = Color.Red,
                 backgroundSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
                 shapeSize = with(rule.density) { DpSize(60.dp, 60.dp).toSize() },
-                shapeAndBackgroundCenter =
-                    with(rule.density) { Offset(30.dp.toPx(), 30.dp.toPx()) },
+                shapeCenter = with(rule.density) { Offset(30.dp.toPx(), 30.dp.toPx()) },
+                backgroundCenter = with(rule.density) { Offset(30.dp.toPx(), 30.dp.toPx()) },
                 antiAliasingGap = with(rule.density) { 2.dp.toPx() }
             )
     }
@@ -955,8 +955,8 @@
                 backgroundColor = Color.Red,
                 backgroundSize = with(rule.density) { DpSize(100.dp, 100.dp).toSize() },
                 shapeSize = with(rule.density) { DpSize(100.dp, 100.dp).toSize() },
-                shapeAndBackgroundCenter =
-                    with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                shapeCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                backgroundCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
                 antiAliasingGap = with(rule.density) { 2.dp.toPx() }
             )
     }
@@ -999,8 +999,8 @@
                 backgroundColor = Color.Red,
                 backgroundSize = with(rule.density) { DpSize(100.dp, 100.dp).toSize() },
                 shapeSize = with(rule.density) { DpSize(100.dp, 100.dp).toSize() },
-                shapeAndBackgroundCenter =
-                    with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                shapeCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
+                backgroundCenter = with(rule.density) { Offset(50.dp.toPx(), 50.dp.toPx()) },
                 antiAliasingGap = with(rule.density) { 2.dp.toPx() }
             )
     }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 0aa561b..870c16a 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -1150,6 +1150,32 @@
         }
     }
 
+    @Test
+    fun modalBottomSheet_testDragHandleClick() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberModalBottomSheetState()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = sheetState,
+                dragHandle = { Box(Modifier.testTag(dragHandleTag).size(dragHandleSize)) },
+            ) {
+                Box(Modifier.fillMaxSize().testTag(sheetTag))
+            }
+        }
+
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(dragHandleTag, useUnmergedTree = true).performClick()
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+    }
+
     private val Bundle.traversalBefore: Int
         get() = getInt("android.view.accessibility.extra.EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL")
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
index b119e6e..ac1c17a 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShortNavigationBarTest.kt
@@ -494,6 +494,40 @@
     }
 
     @Test
+    fun item_customColors() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val customColors =
+                ShortNavigationBarItemDefaults.colors(
+                    selectedIconColor = Color.Red,
+                    unselectedTextColor = Color.Green,
+                )
+
+            ShortNavigationBar {
+                ShortNavigationBarItem(
+                    colors = customColors,
+                    icon = { Truth.assertThat(LocalContentColor.current).isEqualTo(Color.Red) },
+                    label = {
+                        Truth.assertThat(LocalContentColor.current)
+                            .isEqualTo(NavigationBarTokens.ItemActiveLabelTextColor.value)
+                    },
+                    selected = true,
+                    onClick = {}
+                )
+                ShortNavigationBarItem(
+                    colors = customColors,
+                    icon = {
+                        Truth.assertThat(LocalContentColor.current)
+                            .isEqualTo(NavigationBarTokens.ItemInactiveIconColor.value)
+                    },
+                    label = { Truth.assertThat(LocalContentColor.current).isEqualTo(Color.Green) },
+                    selected = false,
+                    onClick = {}
+                )
+            }
+        }
+    }
+
+    @Test
     fun itemContent_topIconPosition_sizeAndPosition() {
         var minSize: Dp? = null
         rule.setMaterialContent(lightColorScheme()) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
index fdfaeea..e568dd5 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WideNavigationRailTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.tokens.NavigationRailBaselineItemTokens
 import androidx.compose.material3.tokens.NavigationRailCollapsedTokens
+import androidx.compose.material3.tokens.NavigationRailColorTokens
 import androidx.compose.material3.tokens.NavigationRailExpandedTokens
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -444,6 +445,40 @@
     }
 
     @Test
+    fun item_customColors() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val customColors =
+                WideNavigationRailItemDefaults.colors(
+                    selectedIconColor = Color.Red,
+                    unselectedTextColor = Color.Green,
+                )
+
+            WideNavigationRail {
+                WideNavigationRailItem(
+                    colors = customColors,
+                    icon = { Truth.assertThat(LocalContentColor.current).isEqualTo(Color.Red) },
+                    label = {
+                        Truth.assertThat(LocalContentColor.current)
+                            .isEqualTo(NavigationRailColorTokens.ItemActiveLabelText.value)
+                    },
+                    selected = true,
+                    onClick = {}
+                )
+                WideNavigationRailItem(
+                    colors = customColors,
+                    icon = {
+                        Truth.assertThat(LocalContentColor.current)
+                            .isEqualTo(NavigationRailColorTokens.ItemInactiveLabelText.value)
+                    },
+                    label = { Truth.assertThat(LocalContentColor.current).isEqualTo(Color.Green) },
+                    selected = false,
+                    onClick = {}
+                )
+            }
+        }
+    }
+
+    @Test
     fun header_position() {
         rule.setMaterialContent(lightColorScheme()) {
             WideNavigationRail(header = { Box(Modifier.testTag("header").size(10.dp)) }) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index 9193761..75b8038 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -1589,6 +1589,7 @@
      * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
      *   app bar when the user flings the app bar itself, or the content below it
      */
+    @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
     @ExperimentalMaterial3Api
     @Composable
     fun enterAlwaysScrollBehavior(
@@ -1597,12 +1598,49 @@
         // TODO Load the motionScheme tokens from the component tokens file
         snapAnimationSpec: AnimationSpec<Float>? = MotionSchemeKeyTokens.DefaultEffects.value(),
         flingAnimationSpec: DecayAnimationSpec<Float>? = rememberSplineBasedDecay()
+    ): TopAppBarScrollBehavior {
+        return enterAlwaysScrollBehavior(
+            state = state,
+            canScroll = canScroll,
+            snapAnimationSpec = snapAnimationSpec,
+            flingAnimationSpec = flingAnimationSpec,
+            reverseLayout = false
+        )
+    }
+
+    /**
+     * Returns a [TopAppBarScrollBehavior]. A top app bar that is set up with this
+     * [TopAppBarScrollBehavior] will immediately collapse when the content is pulled up, and will
+     * immediately appear when the content is pulled down.
+     *
+     * @param state the state object to be used to control or observe the top app bar's scroll
+     *   state. See [rememberTopAppBarState] for a state that is remembered across compositions.
+     * @param canScroll a callback used to determine whether scroll events are to be handled by this
+     *   [EnterAlwaysScrollBehavior]
+     * @param snapAnimationSpec an optional [AnimationSpec] that defines how the top app bar snaps
+     *   to either fully collapsed or fully extended state when a fling or a drag scrolled it into
+     *   an intermediate position
+     * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
+     *   app bar when the user flings the app bar itself, or the content below it
+     * @param reverseLayout indicates that this behavior is applied to a scrollable content that has
+     *   a reversed direction of scrolling and layout
+     */
+    @ExperimentalMaterial3Api
+    @Composable
+    fun enterAlwaysScrollBehavior(
+        state: TopAppBarState = rememberTopAppBarState(),
+        canScroll: () -> Boolean = { true },
+        // TODO Load the motionScheme tokens from the component tokens file
+        snapAnimationSpec: AnimationSpec<Float>? = MotionSchemeKeyTokens.DefaultEffects.value(),
+        flingAnimationSpec: DecayAnimationSpec<Float>? = rememberSplineBasedDecay(),
+        reverseLayout: Boolean = false
     ): TopAppBarScrollBehavior =
         EnterAlwaysScrollBehavior(
             state = state,
             snapAnimationSpec = snapAnimationSpec,
             flingAnimationSpec = flingAnimationSpec,
-            canScroll = canScroll
+            canScroll = canScroll,
+            reverseLayout = reverseLayout
         )
 
     /**
@@ -2811,13 +2849,16 @@
  *   bar when the user flings the app bar itself, or the content below it
  * @param canScroll a callback used to determine whether scroll events are to be handled by this
  *   [EnterAlwaysScrollBehavior]
+ * @param reverseLayout indicates that this behavior is applied to a scrollable content that has a
+ *   reversed direction of scrolling and layout
  */
 @OptIn(ExperimentalMaterial3Api::class)
 private class EnterAlwaysScrollBehavior(
     override val state: TopAppBarState,
     override val snapAnimationSpec: AnimationSpec<Float>?,
     override val flingAnimationSpec: DecayAnimationSpec<Float>?,
-    val canScroll: () -> Boolean = { true }
+    val canScroll: () -> Boolean = { true },
+    val reverseLayout: Boolean = false
 ) : TopAppBarScrollBehavior {
     override val isPinned: Boolean = false
     override var nestedScrollConnection =
@@ -2825,8 +2866,14 @@
             override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                 if (!canScroll()) return Offset.Zero
                 val prevHeightOffset = state.heightOffset
-                state.heightOffset = state.heightOffset + available.y
-                return if (prevHeightOffset != state.heightOffset) {
+                state.heightOffset += available.y
+                // The state's heightOffset is coerce in a minimum value of heightOffsetLimit and a
+                // maximum value 0f, so we check if its value was actually changed after the
+                // available.y was added to it in order to tell if the top app bar is currently
+                // collapsing or expanding.
+                // Note that when the content was set with a revered layout, we always return a
+                // zero offset.
+                return if (!reverseLayout && prevHeightOffset != state.heightOffset) {
                     // We're in the middle of top app bar collapse or expand.
                     // Consume only the scroll on the Y axis.
                     available.copy(x = 0f)
@@ -2849,7 +2896,7 @@
                         state.contentOffset = 0f
                     }
                 }
-                state.heightOffset = state.heightOffset + consumed.y
+                if (!reverseLayout) state.heightOffset += consumed.y
                 return Offset.Zero
             }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
index 8ef2938..8e2eabd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -328,37 +329,59 @@
                 val dismissActionLabel = getString(Strings.BottomSheetDismissDescription)
                 val expandActionLabel = getString(Strings.BottomSheetExpandDescription)
                 Box(
-                    Modifier.align(CenterHorizontally).semantics(mergeDescendants = true) {
-                        with(state) {
-                            // Provides semantics to interact with the bottomsheet if there is more
-                            // than one anchor to swipe to and swiping is enabled.
-                            if (anchoredDraggableState.anchors.size > 1 && sheetSwipeEnabled) {
-                                if (currentValue == PartiallyExpanded) {
-                                    if (anchoredDraggableState.confirmValueChange(Expanded)) {
-                                        expand(expandActionLabel) {
-                                            scope.launch { expand() }
-                                            true
+                    modifier =
+                        Modifier.align(CenterHorizontally)
+                            .clickable {
+                                when (state.currentValue) {
+                                    Expanded ->
+                                        scope.launch {
+                                            if (!state.skipHiddenState) {
+                                                state.hide()
+                                            } else {
+                                                state.partialExpand()
+                                            }
                                         }
-                                    }
-                                } else {
-                                    if (
-                                        anchoredDraggableState.confirmValueChange(PartiallyExpanded)
-                                    ) {
-                                        collapse(partialExpandActionLabel) {
-                                            scope.launch { partialExpand() }
-                                            true
-                                        }
-                                    }
-                                }
-                                if (!state.skipHiddenState) {
-                                    dismiss(dismissActionLabel) {
-                                        scope.launch { hide() }
-                                        true
-                                    }
+                                    PartiallyExpanded -> scope.launch { state.expand() }
+                                    else -> scope.launch { state.show() }
                                 }
                             }
-                        }
-                    },
+                            .semantics(mergeDescendants = true) {
+                                with(state) {
+                                    // Provides semantics to interact with the bottomsheet if there
+                                    // is more than one anchor to swipe to and swiping is enabled.
+                                    if (
+                                        anchoredDraggableState.anchors.size > 1 && sheetSwipeEnabled
+                                    ) {
+                                        if (currentValue == PartiallyExpanded) {
+                                            if (
+                                                anchoredDraggableState.confirmValueChange(Expanded)
+                                            ) {
+                                                expand(expandActionLabel) {
+                                                    scope.launch { expand() }
+                                                    true
+                                                }
+                                            }
+                                        } else {
+                                            if (
+                                                anchoredDraggableState.confirmValueChange(
+                                                    PartiallyExpanded
+                                                )
+                                            ) {
+                                                collapse(partialExpandActionLabel) {
+                                                    scope.launch { partialExpand() }
+                                                    true
+                                                }
+                                            }
+                                        }
+                                        if (!state.skipHiddenState) {
+                                            dismiss(dismissActionLabel) {
+                                                scope.launch { hide() }
+                                                true
+                                            }
+                                        }
+                                    }
+                                }
+                            },
                 ) {
                     dragHandle()
                 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index 3857026..f3190ed 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -902,7 +902,7 @@
                 selected && !enabled -> disabledSelectedDayContentColor
                 inRange && enabled -> dayInSelectionRangeContentColor
                 inRange && !enabled -> disabledDayContentColor
-                isToday -> todayContentColor
+                isToday && enabled -> todayContentColor
                 enabled -> dayContentColor
                 else -> disabledDayContentColor
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
index b0ccf55..7cffbca 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
@@ -189,7 +189,7 @@
         CompositionLocalProvider(LocalUsingExpressiveTheme provides true) {
             MaterialTheme(
                 colorScheme = colorScheme ?: expressiveLightColorScheme(),
-                motionScheme = motionScheme ?: expressiveMotionScheme(),
+                motionScheme = motionScheme ?: MotionScheme.expressiveMotionScheme(),
                 shapes = shapes ?: Shapes(),
                 // TODO: replace with calls to Expressive typography default
                 typography = typography ?: Typography(),
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
index 4185410f7..9e0d86f 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.draggable
@@ -328,35 +329,46 @@
                 val dismissActionLabel = getString(Strings.BottomSheetDismissDescription)
                 val expandActionLabel = getString(Strings.BottomSheetExpandDescription)
                 Box(
-                    Modifier.align(Alignment.CenterHorizontally).semantics(
-                        mergeDescendants = true
-                    ) {
-                        // Provides semantics to interact with the bottomsheet based on its
-                        // current value.
-                        with(sheetState) {
-                            dismiss(dismissActionLabel) {
-                                animateToDismiss()
-                                true
-                            }
-                            if (currentValue == PartiallyExpanded) {
-                                expand(expandActionLabel) {
-                                    if (anchoredDraggableState.confirmValueChange(Expanded)) {
-                                        scope.launch { sheetState.expand() }
-                                    }
-                                    true
-                                }
-                            } else if (hasPartiallyExpandedState) {
-                                collapse(collapseActionLabel) {
-                                    if (
-                                        anchoredDraggableState.confirmValueChange(PartiallyExpanded)
-                                    ) {
-                                        scope.launch { partialExpand() }
-                                    }
-                                    true
+                    modifier =
+                        Modifier.align(Alignment.CenterHorizontally)
+                            .clickable {
+                                when (sheetState.currentValue) {
+                                    Expanded -> scope.launch { sheetState.hide() }
+                                    PartiallyExpanded -> scope.launch { sheetState.expand() }
+                                    else -> scope.launch { sheetState.show() }
                                 }
                             }
-                        }
-                    }
+                            .semantics(mergeDescendants = true) {
+                                // Provides semantics to interact with the bottomsheet based on its
+                                // current value.
+                                with(sheetState) {
+                                    dismiss(dismissActionLabel) {
+                                        animateToDismiss()
+                                        true
+                                    }
+                                    if (currentValue == PartiallyExpanded) {
+                                        expand(expandActionLabel) {
+                                            if (
+                                                anchoredDraggableState.confirmValueChange(Expanded)
+                                            ) {
+                                                scope.launch { sheetState.expand() }
+                                            }
+                                            true
+                                        }
+                                    } else if (hasPartiallyExpandedState) {
+                                        collapse(collapseActionLabel) {
+                                            if (
+                                                anchoredDraggableState.confirmValueChange(
+                                                    PartiallyExpanded
+                                                )
+                                            ) {
+                                                scope.launch { partialExpand() }
+                                            }
+                                            true
+                                        }
+                                    }
+                                }
+                            },
                 ) {
                     dragHandle()
                 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
index b19a579..1edebad 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
@@ -20,13 +20,15 @@
 import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.animation.core.spring
+import androidx.compose.material3.MotionScheme.Companion.expressiveMotionScheme
+import androidx.compose.material3.MotionScheme.Companion.standardMotionScheme
 import androidx.compose.material3.tokens.ExpressiveMotionTokens
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
 import androidx.compose.material3.tokens.StandardMotionTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticCompositionLocalOf
 
 /**
@@ -51,9 +53,6 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberDefaultSpatialSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T>
 
@@ -66,9 +65,6 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberFastSpatialSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> fastSpatialSpec(): FiniteAnimationSpec<T>
 
@@ -81,9 +77,6 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberSlowSpatialSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> slowSpatialSpec(): FiniteAnimationSpec<T>
 
@@ -95,9 +88,6 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberDefaultEffectsSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T>
 
@@ -109,9 +99,6 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberFastEffectsSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> fastEffectsSpec(): FiniteAnimationSpec<T>
 
@@ -123,199 +110,147 @@
      *
      * [T] is the generic data type that will be animated by the system, as long as the appropriate
      * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
-     *
-     * When called from a Composable, use [rememberSlowEffectsSpec] extension to ensure that the
-     * returned animation spec is remembered across compositions.
      */
     fun <T> slowEffectsSpec(): FiniteAnimationSpec<T>
+
+    companion object {
+
+        /** Returns a standard Material motion scheme. */
+        @Suppress("UNCHECKED_CAST")
+        @ExperimentalMaterial3ExpressiveApi
+        fun standardMotionScheme(): MotionScheme =
+            object : MotionScheme {
+                private val defaultSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringDefaultSpatialDamping,
+                        stiffness = StandardMotionTokens.SpringDefaultSpatialStiffness
+                    )
+
+                private val fastSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringFastSpatialDamping,
+                        stiffness = StandardMotionTokens.SpringFastSpatialStiffness
+                    )
+
+                private val slowSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringSlowSpatialDamping,
+                        stiffness = StandardMotionTokens.SpringSlowSpatialStiffness
+                    )
+
+                private val defaultEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringDefaultEffectsDamping,
+                        stiffness = StandardMotionTokens.SpringDefaultEffectsStiffness
+                    )
+
+                private val fastEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringFastEffectsDamping,
+                        stiffness = StandardMotionTokens.SpringFastEffectsStiffness
+                    )
+
+                private val slowEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = StandardMotionTokens.SpringSlowEffectsDamping,
+                        stiffness = StandardMotionTokens.SpringSlowEffectsStiffness
+                    )
+
+                override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
+                    return defaultSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
+                    return fastSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
+                    return slowSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
+                    return defaultEffectsSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
+                    return fastEffectsSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
+                    return slowEffectsSpec as FiniteAnimationSpec<T>
+                }
+            }
+
+        /** Returns an expressive Material motion scheme. */
+        @Suppress("UNCHECKED_CAST")
+        @ExperimentalMaterial3ExpressiveApi
+        fun expressiveMotionScheme(): MotionScheme =
+            object : MotionScheme {
+
+                private val defaultSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringDefaultSpatialDamping,
+                        stiffness = ExpressiveMotionTokens.SpringDefaultSpatialStiffness
+                    )
+
+                private val fastSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringFastSpatialDamping,
+                        stiffness = ExpressiveMotionTokens.SpringFastSpatialStiffness
+                    )
+
+                private val slowSpatialSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringSlowSpatialDamping,
+                        stiffness = ExpressiveMotionTokens.SpringSlowSpatialStiffness
+                    )
+
+                private val defaultEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringDefaultEffectsDamping,
+                        stiffness = ExpressiveMotionTokens.SpringDefaultEffectsStiffness
+                    )
+
+                private val fastEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringFastEffectsDamping,
+                        stiffness = ExpressiveMotionTokens.SpringFastEffectsStiffness
+                    )
+
+                private val slowEffectsSpec =
+                    spring<Any>(
+                        dampingRatio = ExpressiveMotionTokens.SpringSlowEffectsDamping,
+                        stiffness = ExpressiveMotionTokens.SpringSlowEffectsStiffness
+                    )
+
+                override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
+                    return defaultSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
+                    return fastSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
+                    return slowSpatialSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
+                    return defaultEffectsSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
+                    return fastEffectsSpec as FiniteAnimationSpec<T>
+                }
+
+                override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
+                    return slowEffectsSpec as FiniteAnimationSpec<T>
+                }
+            }
+    }
 }
 
 /**
- * A default spatial motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system, as long as the appropriate
- * [TwoWayConverter] for converting the data to and from an [AnimationVector] is supplied.
- *
- * @see [MotionScheme.defaultSpatialSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberDefaultSpatialSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = defaultSpatialSpec()
-        spec
-    }
-
-/**
- * A fast spatial motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system.
- *
- * @see [MotionScheme.fastSpatialSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberFastSpatialSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = fastSpatialSpec()
-        spec
-    }
-
-/**
- * A slow spatial motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system.
- *
- * @see [MotionScheme.slowSpatialSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberSlowSpatialSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = slowSpatialSpec()
-        spec
-    }
-
-/**
- * A default effects motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system.
- *
- * @see [MotionScheme.defaultEffectsSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberDefaultEffectsSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = defaultEffectsSpec()
-        spec
-    }
-
-/**
- * A fast effects motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system.
- *
- * @see [MotionScheme.fastEffectsSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberFastEffectsSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = fastEffectsSpec()
-        spec
-    }
-
-/**
- * A slow effects motion [FiniteAnimationSpec] that is remembered across compositions.
- *
- * [T] is the generic data type that will be animated by the system.
- *
- * @see [MotionScheme.slowEffectsSpec]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-inline fun <reified T> MotionScheme.rememberSlowEffectsSpec() =
-    remember(this, T::class) {
-        val spec: FiniteAnimationSpec<T> = slowEffectsSpec()
-        spec
-    }
-
-/** Returns a standard Material motion scheme. */
-@ExperimentalMaterial3ExpressiveApi
-fun standardMotionScheme(): MotionScheme =
-    object : MotionScheme {
-        override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringDefaultSpatialDamping,
-                stiffness = StandardMotionTokens.SpringDefaultSpatialStiffness
-            )
-        }
-
-        override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringFastSpatialDamping,
-                stiffness = StandardMotionTokens.SpringFastSpatialStiffness
-            )
-        }
-
-        override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringSlowSpatialDamping,
-                stiffness = StandardMotionTokens.SpringSlowSpatialStiffness
-            )
-        }
-
-        override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringDefaultEffectsDamping,
-                stiffness = StandardMotionTokens.SpringDefaultEffectsStiffness
-            )
-        }
-
-        override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringFastEffectsDamping,
-                stiffness = StandardMotionTokens.SpringFastEffectsStiffness
-            )
-        }
-
-        override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = StandardMotionTokens.SpringSlowEffectsDamping,
-                stiffness = StandardMotionTokens.SpringSlowEffectsStiffness
-            )
-        }
-    }
-
-/** Returns an expressive Material motion scheme. */
-@ExperimentalMaterial3ExpressiveApi
-fun expressiveMotionScheme(): MotionScheme =
-    object : MotionScheme {
-        override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringDefaultSpatialDamping,
-                stiffness = ExpressiveMotionTokens.SpringDefaultSpatialStiffness
-            )
-        }
-
-        override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringFastSpatialDamping,
-                stiffness = ExpressiveMotionTokens.SpringFastSpatialStiffness
-            )
-        }
-
-        override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringSlowSpatialDamping,
-                stiffness = ExpressiveMotionTokens.SpringSlowSpatialStiffness
-            )
-        }
-
-        override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringDefaultEffectsDamping,
-                stiffness = ExpressiveMotionTokens.SpringDefaultEffectsStiffness
-            )
-        }
-
-        override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringFastEffectsDamping,
-                stiffness = ExpressiveMotionTokens.SpringFastEffectsStiffness
-            )
-        }
-
-        override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
-            return spring(
-                dampingRatio = ExpressiveMotionTokens.SpringSlowEffectsDamping,
-                stiffness = ExpressiveMotionTokens.SpringSlowEffectsStiffness
-            )
-        }
-    }
-
-/**
  * CompositionLocal used to pass [MotionScheme] down the tree.
  *
  * Setting the value here is typically done as part of [MaterialTheme]. To retrieve the current
@@ -324,7 +259,7 @@
 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
 @get:ExperimentalMaterial3ExpressiveApi
 @ExperimentalMaterial3ExpressiveApi
-internal val LocalMotionScheme = staticCompositionLocalOf { standardMotionScheme() }
+internal val LocalMotionScheme = staticCompositionLocalOf { MotionScheme.standardMotionScheme() }
 
 /**
  * Helper function for component motion tokens.
@@ -336,19 +271,16 @@
  *
  * @param value the token's value
  */
-@Composable
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Stable
-internal inline fun <reified T> MotionScheme.fromToken(
-    value: MotionSchemeKeyTokens
-): FiniteAnimationSpec<T> {
+internal fun <T> MotionScheme.fromToken(value: MotionSchemeKeyTokens): FiniteAnimationSpec<T> {
     return when (value) {
-        MotionSchemeKeyTokens.DefaultSpatial -> rememberDefaultSpatialSpec()
-        MotionSchemeKeyTokens.FastSpatial -> rememberFastSpatialSpec()
-        MotionSchemeKeyTokens.SlowSpatial -> rememberSlowSpatialSpec()
-        MotionSchemeKeyTokens.DefaultEffects -> rememberDefaultEffectsSpec()
-        MotionSchemeKeyTokens.FastEffects -> rememberFastEffectsSpec()
-        MotionSchemeKeyTokens.SlowEffects -> rememberSlowEffectsSpec()
+        MotionSchemeKeyTokens.DefaultSpatial -> defaultSpatialSpec()
+        MotionSchemeKeyTokens.FastSpatial -> fastSpatialSpec()
+        MotionSchemeKeyTokens.SlowSpatial -> slowSpatialSpec()
+        MotionSchemeKeyTokens.DefaultEffects -> defaultEffectsSpec()
+        MotionSchemeKeyTokens.FastEffects -> fastEffectsSpec()
+        MotionSchemeKeyTokens.SlowEffects -> slowEffectsSpec()
     }
 }
 
@@ -357,6 +289,7 @@
  * [MotionScheme].
  */
 @Composable
+@ReadOnlyComposable
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
-internal inline fun <reified T> MotionSchemeKeyTokens.value(): FiniteAnimationSpec<T> =
+internal fun <T> MotionSchemeKeyTokens.value(): FiniteAnimationSpec<T> =
     MaterialTheme.motionScheme.fromToken(this)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 771d263..af4c3ad 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -221,7 +221,6 @@
  *   respond to user input, and it will appear visually disabled and disabled to accessibility
  *   services
  * @param label the text label for this item
- * @param badge optional badge to show on this item, typically a [Badge]
  * @param iconPosition the [NavigationItemIconPosition] for this icon
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  *   for this item. You can create and pass in your own `remember`ed instance to observe
@@ -245,12 +244,12 @@
     modifier: Modifier,
     enabled: Boolean,
     label: @Composable (() -> Unit)?,
-    badge: (@Composable () -> Unit)?,
     iconPosition: NavigationItemIconPosition,
     interactionSource: MutableInteractionSource
 ) {
-    val iconWithBadge: @Composable () -> Unit = {
-        StyledIcon(selected, icon, colors, enabled, badge)
+    val iconColor = colors.iconColor(selected = selected, enabled = enabled)
+    val styledIcon: @Composable () -> Unit = {
+        CompositionLocalProvider(LocalContentColor provides iconColor, content = icon)
     }
     val styledLabel: @Composable (() -> Unit)? =
         if (label == null) {
@@ -303,7 +302,7 @@
             interactionSource = offsetInteractionSource ?: interactionSource,
             indicatorColor = colors.selectedIndicatorColor,
             indicatorShape = indicatorShape,
-            icon = iconWithBadge,
+            icon = styledIcon,
             iconPosition = iconPosition,
             label = styledLabel,
             indicatorAnimationProgress = { indicatorAnimationProgress.value.coerceAtLeast(0f) },
@@ -343,12 +342,12 @@
     modifier: Modifier,
     enabled: Boolean,
     label: @Composable (() -> Unit),
-    badge: (@Composable () -> Unit)?,
     iconPosition: NavigationItemIconPosition,
     interactionSource: MutableInteractionSource
 ) {
-    val iconWithBadge: @Composable () -> Unit = {
-        StyledIcon(selected, icon, colors, enabled, badge)
+    val iconColor = colors.iconColor(selected = selected, enabled = enabled)
+    val styledIcon: @Composable () -> Unit = {
+        CompositionLocalProvider(LocalContentColor provides iconColor, content = icon)
     }
 
     var itemWidth by remember { mutableIntStateOf(0) }
@@ -451,7 +450,7 @@
             indicatorColor = colors.selectedIndicatorColor,
             indicatorShape = indicatorShape,
             indicatorAnimationProgress = { indicatorAnimationProgress.value.coerceAtLeast(0f) },
-            icon = iconWithBadge,
+            icon = styledIcon,
             iconPosition = iconPosition,
             iconPositionProgress = { iconPositionProgress.coerceAtLeast(0f) },
             labelTopIcon = labelTopIcon,
@@ -676,20 +675,15 @@
         @Suppress("NAME_SHADOWING") val indicatorAnimationProgress = indicatorAnimationProgress()
         val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
         // When measuring icon, account for the indicator in its constraints.
-        val iconConstraints =
-            looseConstraints.offset(
-                horizontal = -(indicatorHorizontalPadding * 2).roundToPx(),
-                vertical = -(indicatorVerticalPadding * 2).roundToPx()
-            )
         val iconPlaceable =
-            measurables.fastFirst { it.layoutId == IconLayoutIdTag }.measure(iconConstraints)
+            measurables.fastFirst { it.layoutId == IconLayoutIdTag }.measure(looseConstraints)
         // When measuring the label, account for the indicator, the icon, and the padding between
         // icon and label.
         val labelPlaceable =
             measurables
                 .fastFirst { it.layoutId == LabelLayoutIdTag }
                 .measure(
-                    iconConstraints.offset(
+                    looseConstraints.offset(
                         horizontal =
                             -(iconPlaceable.width + startIconToLabelHorizontalPadding.roundToPx())
                     )
@@ -1093,26 +1087,6 @@
 }
 
 @Composable
-private fun StyledIcon(
-    selected: Boolean,
-    icon: @Composable () -> Unit,
-    colors: NavigationItemColors,
-    enabled: Boolean,
-    badge: (@Composable () -> Unit)?,
-) {
-    val iconColor = colors.iconColor(selected = selected, enabled = enabled)
-    val styledIcon: @Composable () -> Unit = {
-        CompositionLocalProvider(LocalContentColor provides iconColor, content = icon)
-    }
-
-    if (badge != null) {
-        BadgedBox(badge = { badge() }) { styledIcon() }
-    } else {
-        styledIcon()
-    }
-}
-
-@Composable
 private fun StyledLabel(
     selected: Boolean,
     labelTextStyle: TextStyle,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
index 54aa142..cd76230 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ShortNavigationBar.kt
@@ -173,7 +173,6 @@
  * @param enabled controls the enabled state of this item. When `false`, this component will not
  *   respond to user input, and it will appear visually disabled and disabled to accessibility
  *   services.
- * @param badge optional badge to show on this item, typically a [Badge]
  * @param iconPosition the [NavigationItemIconPosition] for the icon
  * @param colors [NavigationItemColors] that will be used to resolve the colors used for this item
  *   in different states. See [ShortNavigationBarItemDefaults.colors]
@@ -191,7 +190,6 @@
     label: @Composable (() -> Unit)?,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    badge: (@Composable () -> Unit)? = null,
     iconPosition: NavigationItemIconPosition = NavigationItemIconPosition.Top,
     colors: NavigationItemColors = ShortNavigationBarItemDefaults.colors(),
     interactionSource: MutableInteractionSource? = null,
@@ -229,7 +227,6 @@
         modifier = modifier,
         enabled = enabled,
         label = label,
-        badge = badge,
         iconPosition = iconPosition,
         interactionSource = interactionSource,
     )
@@ -268,6 +265,39 @@
      */
     @Composable fun colors() = MaterialTheme.colorScheme.defaultShortNavigationBarItemColors
 
+    /**
+     * Creates a [NavigationItemColors] with the provided colors according to the Material
+     * specification.
+     *
+     * @param selectedIconColor the color to use for the icon when the item is selected.
+     * @param selectedTextColor the color to use for the text label when the item is selected.
+     * @param selectedIndicatorColor the color to use for the indicator when the item is selected.
+     * @param unselectedIconColor the color to use for the icon when the item is unselected.
+     * @param unselectedTextColor the color to use for the text label when the item is unselected.
+     * @param disabledIconColor the color to use for the icon when the item is disabled.
+     * @param disabledTextColor the color to use for the text label when the item is disabled.
+     * @return the resulting [NavigationItemColors] used for [ShortNavigationBarItem]
+     */
+    @Composable
+    fun colors(
+        selectedIconColor: Color = NavigationBarTokens.ItemActiveIconColor.value,
+        selectedTextColor: Color = NavigationBarTokens.ItemActiveLabelTextColor.value,
+        selectedIndicatorColor: Color = NavigationBarTokens.ItemActiveIndicatorColor.value,
+        unselectedIconColor: Color = NavigationBarTokens.ItemInactiveIconColor.value,
+        unselectedTextColor: Color = NavigationBarTokens.ItemInactiveLabelTextColor.value,
+        disabledIconColor: Color = unselectedIconColor.copy(alpha = DisabledAlpha),
+        disabledTextColor: Color = unselectedTextColor.copy(alpha = DisabledAlpha),
+    ): NavigationItemColors =
+        MaterialTheme.colorScheme.defaultShortNavigationBarItemColors.copy(
+            selectedIconColor = selectedIconColor,
+            selectedTextColor = selectedTextColor,
+            selectedIndicatorColor = selectedIndicatorColor,
+            unselectedIconColor = unselectedIconColor,
+            unselectedTextColor = unselectedTextColor,
+            disabledIconColor = disabledIconColor,
+            disabledTextColor = disabledTextColor,
+        )
+
     internal val ColorScheme.defaultShortNavigationBarItemColors: NavigationItemColors
         get() {
             return defaultShortNavigationBarItemColorsCached
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
index 63959a5..e2e3cae 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
@@ -477,7 +477,7 @@
     /** Default size for the leading button end corners and trailing button start corners */
     // TODO update token to dp size and use it here
     val InnerCornerSize = SplitButtonSmallTokens.InnerCornerSize
-    private val InnerCornerSizePressed = ShapeDefaults.CornerMedium
+    private val InnerCornerSizePressed = SplitButtonSmallTokens.InnerCornerPressedSize
 
     /**
      * Default percentage size for the leading button start corners and trailing button end corners
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
index 2ad49ae..76c9a44 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
@@ -41,6 +41,7 @@
 import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.WideNavigationRailItemDefaults.defaultWideNavigationRailItemColors
 import androidx.compose.material3.internal.DraggableAnchors
 import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.draggableAnchors
@@ -673,7 +674,6 @@
  * @param enabled controls the enabled state of this item. When `false`, this component will not
  *   respond to user input, and it will appear visually disabled and disabled to accessibility
  *   services.
- * @param badge optional badge to show on this item, typically a [Badge]
  * @param railExpanded whether the associated [WideNavigationRail] is expanded or collapsed
  * @param iconPosition the [NavigationItemIconPosition] for the icon
  * @param colors [NavigationItemColors] that will be used to resolve the colors used for this item
@@ -692,7 +692,6 @@
     label: @Composable (() -> Unit)?,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    badge: (@Composable () -> Unit)? = null,
     railExpanded: Boolean = false,
     iconPosition: NavigationItemIconPosition =
         WideNavigationRailItemDefaults.iconPositionFor(railExpanded),
@@ -724,7 +723,6 @@
             modifier = modifier,
             enabled = enabled,
             label = label,
-            badge = badge,
             iconPosition = iconPosition,
             interactionSource = interactionSource,
         )
@@ -746,7 +744,6 @@
             modifier = modifier,
             enabled = enabled,
             label = label,
-            badge = badge,
             iconPosition = iconPosition,
             interactionSource = interactionSource,
         )
@@ -865,6 +862,32 @@
      */
     @Composable fun colors() = MaterialTheme.colorScheme.defaultWideWideNavigationRailColors
 
+    /**
+     * Creates a [WideNavigationRailColors] with the provided colors according to the Material
+     * specification.
+     *
+     * @param containerColor the color used for the background of a non-modal wide navigation rail.
+     * @param contentColor the preferred color for content inside a wide navigation rail. Defaults
+     *   to either the matching content color for [containerColor], or to the current
+     *   [LocalContentColor] if [containerColor] is not a color from the theme
+     * @param modalContainerColor the color used for the background of a modal wide navigation rail.
+     * @param modalScrimColor the color used for the scrim overlay for background content of a modal
+     *   wide navigation rail
+     */
+    @Composable
+    fun colors(
+        containerColor: Color = WideNavigationRailDefaults.containerColor,
+        contentColor: Color = contentColorFor(containerColor),
+        modalContainerColor: Color = NavigationRailExpandedTokens.ModalContainerColor.value,
+        modalScrimColor: Color = ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
+    ): WideNavigationRailColors =
+        MaterialTheme.colorScheme.defaultWideWideNavigationRailColors.copy(
+            containerColor = containerColor,
+            contentColor = contentColor,
+            modalContainerColor = modalContainerColor,
+            modalScrimColor = modalScrimColor
+        )
+
     private val containerColor: Color
         @Composable get() = NavigationRailCollapsedTokens.ContainerColor.value
 
@@ -900,6 +923,39 @@
      */
     @Composable fun colors() = MaterialTheme.colorScheme.defaultWideNavigationRailItemColors
 
+    /**
+     * Creates a [NavigationItemColors] with the provided colors according to the Material
+     * specification.
+     *
+     * @param selectedIconColor the color to use for the icon when the item is selected.
+     * @param selectedTextColor the color to use for the text label when the item is selected.
+     * @param selectedIndicatorColor the color to use for the indicator when the item is selected.
+     * @param unselectedIconColor the color to use for the icon when the item is unselected.
+     * @param unselectedTextColor the color to use for the text label when the item is unselected.
+     * @param disabledIconColor the color to use for the icon when the item is disabled.
+     * @param disabledTextColor the color to use for the text label when the item is disabled.
+     * @return the resulting [NavigationItemColors] used for [WideNavigationRailItem]
+     */
+    @Composable
+    fun colors(
+        selectedIconColor: Color = NavigationRailColorTokens.ItemActiveIcon.value,
+        selectedTextColor: Color = NavigationRailColorTokens.ItemActiveLabelText.value,
+        selectedIndicatorColor: Color = NavigationRailColorTokens.ItemActiveIndicator.value,
+        unselectedIconColor: Color = NavigationRailColorTokens.ItemInactiveIcon.value,
+        unselectedTextColor: Color = NavigationRailColorTokens.ItemInactiveLabelText.value,
+        disabledIconColor: Color = unselectedIconColor.copy(alpha = DisabledAlpha),
+        disabledTextColor: Color = unselectedTextColor.copy(alpha = DisabledAlpha),
+    ): NavigationItemColors =
+        MaterialTheme.colorScheme.defaultWideNavigationRailItemColors.copy(
+            selectedIconColor = selectedIconColor,
+            selectedTextColor = selectedTextColor,
+            selectedIndicatorColor = selectedIndicatorColor,
+            unselectedIconColor = unselectedIconColor,
+            unselectedTextColor = unselectedTextColor,
+            disabledIconColor = disabledIconColor,
+            disabledTextColor = disabledTextColor,
+        )
+
     private val ColorScheme.defaultWideNavigationRailItemColors: NavigationItemColors
         get() {
             return defaultWideNavigationRailItemColorsCached
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
index 8b85857..db345ee 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// VERSION: v0_14_0
+// VERSION: 7_0_1
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -26,10 +26,14 @@
     val BetweenSpace = 2.0.dp
     val ContainerHeight = 40.0.dp
     val ContainerShape = ShapeKeyTokens.CornerFull
+    val InnerCornerFocusedSize = ShapeDefaults.CornerMedium
+    val InnerCornerHoveredSize = ShapeDefaults.CornerMedium
+    val InnerCornerPressedSize = ShapeDefaults.CornerMedium
     val InnerCornerSize = ShapeDefaults.CornerExtraSmall
     val LeadingButtonLeadingSpace = 16.0.dp
     val LeadingButtonTrailingSpace = 12.0.dp
     val TrailingIconSize = 22.0.dp
+    val TrailingInnerCornerSelectedShapePercent = 50.0f
     val TrailingButtonLeadingSpace = 13.0.dp
     val TrailingButtonTrailingSpace = 13.0.dp
 }
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableFlowOperatorDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableFlowOperatorDetector.kt
index 2af4535..91cdcf9 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableFlowOperatorDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableFlowOperatorDetector.kt
@@ -35,7 +35,7 @@
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.impl.compiled.ClsMethodImpl
 import java.util.EnumSet
-import kotlinx.metadata.KmClassifier
+import kotlin.metadata.KmClassifier
 import org.jetbrains.kotlin.psi.KtNamedFunction
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UMethod
diff --git a/compose/runtime/runtime-tracing/build.gradle b/compose/runtime/runtime-tracing/build.gradle
index d6c37d5..d436226 100644
--- a/compose/runtime/runtime-tracing/build.gradle
+++ b/compose/runtime/runtime-tracing/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -47,6 +49,7 @@
 androidx {
     name = "Compose Runtime: Tracing"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
     inceptionYear = "2022"
     description = "Additional tracing in Compose"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AssertShapeTest.kt b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AssertShapeTest.kt
index 8bcd11e..130b044 100644
--- a/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AssertShapeTest.kt
+++ b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AssertShapeTest.kt
@@ -79,9 +79,10 @@
             shape = CircleShape,
             shapeColor = Color.Red,
             shapeSize = Size(50f, 40f),
+            shapeCenter = Offset(60f, 40f),
             backgroundShape = RectangleShape,
             backgroundSize = Size(40f, 60f),
-            anchor = Offset(60f, 40f),
+            backgroundCenter = Offset(60f, 40f),
             antiAliasingGap = 1.21f,
         )
     }
@@ -98,6 +99,104 @@
         )
     }
 
+    @Test
+    fun testAssertShape_rectOnRect_partialOverlap_1() =
+        testAssertShape_partialOverlap_1(RectangleShape, RectangleShape)
+
+    @Test
+    fun testAssertShape_circleOnRect_partialOverlap_1() =
+        testAssertShape_partialOverlap_1(CircleShape, RectangleShape, 1.2f)
+
+    @Test
+    fun testAssertShape_circleOnCircle_partialOverlap_1() =
+        testAssertShape_partialOverlap_1(CircleShape, CircleShape, 1.2f)
+
+    private fun testAssertShape_partialOverlap_1(
+        shape: Shape,
+        backgroundShape: Shape,
+        antiAliasingGap: Float = 1f,
+    ) {
+        //    +-----+
+        // +--| . . |--+
+        // |  |     |  |
+        // +--| . . |--+
+        //    +-----+
+        testAssertShape(
+            shape = shape,
+            shapeColor = Color.Red,
+            shapeSize = Size(20f, 30f),
+            backgroundShape = backgroundShape,
+            backgroundSize = Size(30f, 20f),
+            antiAliasingGap = antiAliasingGap,
+        )
+    }
+
+    @Test
+    fun testAssertShape_rectOnRect_partialOverlap_2() =
+        testAssertShape_partialOverlap_2(RectangleShape, RectangleShape)
+
+    @Test
+    fun testAssertShape_circleOnRect_partialOverlap_2() =
+        testAssertShape_partialOverlap_2(CircleShape, RectangleShape, 1.2f)
+
+    @Test
+    fun testAssertShape_circleOnCircle_partialOverlap_2() =
+        testAssertShape_partialOverlap_2(CircleShape, CircleShape, 1.2f)
+
+    private fun testAssertShape_partialOverlap_2(
+        shape: Shape,
+        backgroundShape: Shape,
+        antiAliasingGap: Float = 1f,
+    ) {
+        //    +-----+
+        //    |  + .|----+
+        //    |  .  |    |
+        //    |  + .|----+
+        //    +-----+
+        testAssertShape(
+            shape = shape,
+            shapeColor = Color.Red,
+            shapeSize = Size(20f, 30f),
+            backgroundShape = backgroundShape,
+            backgroundSize = Size(30f, 20f),
+            backgroundCenter = Offset(65f, 50f),
+            antiAliasingGap = antiAliasingGap,
+        )
+    }
+
+    @Test
+    fun testAssertShape_rectOnRect_partialOverlap_3() =
+        testAssertShape_partialOverlap_3(RectangleShape, RectangleShape)
+
+    @Test
+    fun testAssertShape_circleOnRect_partialOverlap_3() =
+        testAssertShape_partialOverlap_3(CircleShape, RectangleShape, 1.2f)
+
+    @Test
+    fun testAssertShape_circleOnCircle_partialOverlap_3() =
+        testAssertShape_partialOverlap_3(CircleShape, CircleShape, 1.2f)
+
+    private fun testAssertShape_partialOverlap_3(
+        shape: Shape,
+        backgroundShape: Shape,
+        antiAliasingGap: Float = 1f,
+    ) {
+        //    +-----+
+        //    |  + .|---+
+        //    |  .  |   |
+        //    +-----+   |
+        //       +------+
+        testAssertShape(
+            shape = shape,
+            shapeColor = Color.Red,
+            shapeSize = Size(20f, 20f),
+            backgroundShape = backgroundShape,
+            backgroundSize = Size(20f, 20f),
+            backgroundCenter = Offset(60f, 60f),
+            antiAliasingGap = antiAliasingGap,
+        )
+    }
+
     /**
      * Tests [ImageBitmap.assertShape] by creating a bitmap reflecting the asserted values, and then
      * asserting that bitmap with assertShape.
@@ -107,10 +206,11 @@
         shape: Shape,
         shapeColor: Color,
         shapeSize: Size,
+        shapeCenter: Offset = Offset(bitmapSize.width / 2f, bitmapSize.height / 2f),
         backgroundShape: Shape = RectangleShape,
         backgroundColor: Color = Color.Yellow,
         backgroundSize: Size = shapeSize,
-        anchor: Offset = Offset(bitmapSize.width / 2f, bitmapSize.height / 2f),
+        backgroundCenter: Offset = Offset(bitmapSize.width / 2f, bitmapSize.height / 2f),
         antiAliasingGap: Float = 1f,
     ) {
         val bitmap =
@@ -119,20 +219,22 @@
                 shape = shape,
                 shapeColor = shapeColor,
                 shapeSize = shapeSize,
+                shapeCenter = shapeCenter,
                 backgroundShape = backgroundShape,
                 backgroundColor = backgroundColor,
                 backgroundSize = backgroundSize,
-                anchor = anchor
+                backgroundCenter = backgroundCenter,
             )
         bitmap.assertShape(
             density = Density(1f),
             shape = shape,
             shapeColor = shapeColor,
-            backgroundColor = backgroundColor,
-            backgroundShape = backgroundShape,
-            backgroundSize = backgroundSize,
             shapeSize = shapeSize,
-            shapeAndBackgroundCenter = anchor,
+            shapeCenter = shapeCenter,
+            backgroundShape = backgroundShape,
+            backgroundColor = backgroundColor,
+            backgroundSize = backgroundSize,
+            backgroundCenter = backgroundCenter,
             antiAliasingGap = antiAliasingGap,
         )
     }
@@ -160,10 +262,11 @@
                 shape = RectangleShape,
                 shapeColor = Color.Red,
                 shapeSize = Size(4f, 4f),
+                shapeCenter = Offset(6f, 4f),
                 backgroundShape = RectangleShape,
                 backgroundColor = Color.Yellow,
                 backgroundSize = Size(8f, 6f),
-                anchor = Offset(6f, 4f)
+                backgroundCenter = Offset(6f, 4f),
             )
         bitmap.assertPixels(IntSize(10, 10)) { pos ->
             // If the pixel is within the (larger) background area..
@@ -191,10 +294,11 @@
             shape = RectangleShape,
             shapeColor = Color.Red,
             shapeSize = Size(4f, 4f),
+            shapeCenter = Offset(6f, 4f),
             backgroundShape = RectangleShape,
             backgroundColor = Color.Yellow,
             backgroundSize = Size(8f, 6f),
-            anchor = Offset(6f, 4f)
+            backgroundCenter = Offset(6f, 4f),
         )
     }
 
@@ -203,15 +307,16 @@
         shape: Shape,
         shapeColor: Color,
         shapeSize: Size,
+        shapeCenter: Offset,
         backgroundShape: Shape,
         backgroundColor: Color,
         backgroundSize: Size,
-        anchor: Offset,
+        backgroundCenter: Offset,
     ): ImageBitmap {
         val bitmap = ImageBitmap(bitmapSize.width, bitmapSize.height)
         val canvas = Canvas(bitmap)
-        canvas.drawShape(backgroundShape, backgroundColor, backgroundSize, anchor)
-        canvas.drawShape(shape, shapeColor, shapeSize, anchor)
+        canvas.drawShape(backgroundShape, backgroundColor, backgroundSize, backgroundCenter)
+        canvas.drawShape(shape, shapeColor, shapeSize, shapeCenter)
         return bitmap
     }
 
@@ -221,11 +326,11 @@
         shape: Shape,
         color: Color,
         size: Size,
-        anchor: Offset,
+        center: Offset,
         density: Density = Density(1f),
         layoutDirection: LayoutDirection = LayoutDirection.Ltr
     ) {
-        val backgroundOffset = anchor - (size / 2f).asOffset()
+        val backgroundOffset = center - (size / 2f).asOffset()
         val backgroundOutline = shape.createOutline(size, layoutDirection, density)
         CanvasDrawScope().draw(density, layoutDirection, this, size) {
             translate(backgroundOffset.x, backgroundOffset.y) {
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.android.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.android.kt
index 59d784a..3f35bca 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.android.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.android.kt
@@ -17,6 +17,7 @@
 package androidx.compose.testutils
 
 import android.graphics.Bitmap
+import androidx.annotation.VisibleForTesting
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
@@ -36,6 +37,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -120,115 +122,6 @@
 }
 
 /**
- * Tests to see if the given point is within the path. (That is, whether the point would be in the
- * visible portion of the path if the path was used with [Canvas.clipPath].)
- *
- * The `point` argument is interpreted as an offset from the origin.
- *
- * Returns true if the point is in the path, and false otherwise.
- */
-fun Path.contains(offset: Offset): Boolean {
-    val path = android.graphics.Path()
-    path.addRect(
-        /* left = */ offset.x - 0.01f,
-        /* top = */ offset.y - 0.01f,
-        /* right = */ offset.x + 0.01f,
-        /* bottom = */ offset.y + 0.01f,
-        /* dir = */ android.graphics.Path.Direction.CW
-    )
-    if (path.op(asAndroidPath(), android.graphics.Path.Op.INTERSECT)) {
-        return !path.isEmpty
-    }
-    return false
-}
-
-/**
- * Asserts that the given [shape] is drawn in the bitmap with size [shapeSize] in the color
- * [shapeColor], in front of the [backgroundShape] that has size [backgroundSize].
- *
- * The size of a [Shape] is its rectangular bounding box. The centers of both the [shape] and the
- * [backgroundShape] are at [shapeAndBackgroundCenter]. Pixels that are outside the background area
- * are not checked. Pixels that are outside the [shape] and inside the [backgroundShape] must be
- * [backgroundColor]. Pixels that are inside the [shape] must be [shapeColor].
- *
- * To avoid the pixels on the edge of a shape, where anti-aliasing means the pixel is neither the
- * shape color nor the background color, a gap size can be given with [antiAliasingGap]. Pixels that
- * are close to the border of the shape are not checked. A larger [antiAliasingGap] means more
- * pixels are left unchecked, and a gap of 0 pixels means all pixels are tested.
- *
- * @param density current [Density] or the screen
- * @param shape the [Shape] of the foreground
- * @param shapeColor the color of the foreground shape
- * @param shapeSize the [Size] of the [shape]
- * @param shapeAndBackgroundCenter the center of both the [shape] and the [backgroundShape]
- * @param backgroundShape the [Shape] of the background
- * @param backgroundColor the color of the background shape
- * @param backgroundSize the [Size] of the [backgroundShape]
- * @param antiAliasingGap The size of the border area from the shape outline to leave it untested as
- *   it is likely anti-aliased. Only works for convex shapes. The default is 1 pixel
- */
-// TODO (mount, malkov) : to investigate why it flakes when shape is not rect
-fun ImageBitmap.assertShape(
-    density: Density,
-    shape: Shape,
-    shapeColor: Color,
-    backgroundColor: Color,
-    backgroundShape: Shape = RectangleShape,
-    backgroundSize: Size = Size(width.toFloat(), height.toFloat()),
-    shapeSize: Size = backgroundSize,
-    shapeAndBackgroundCenter: Offset = Offset(width / 2f, height / 2f),
-    antiAliasingGap: Float = 1.0f
-) {
-    val pixels = toPixelMap()
-
-    // the bounding box of the foreground shape in the bitmap
-    val foregroundBounds =
-        Rect(
-            left = shapeAndBackgroundCenter.x - shapeSize.width / 2f,
-            top = shapeAndBackgroundCenter.y - shapeSize.height / 2f,
-            right = shapeAndBackgroundCenter.x + shapeSize.width / 2f,
-            bottom = shapeAndBackgroundCenter.y + shapeSize.height / 2f,
-        )
-    // the bounding box of the background shape in the bitmap
-    val backgroundBounds =
-        Rect(
-            left = shapeAndBackgroundCenter.x - backgroundSize.width / 2f,
-            top = shapeAndBackgroundCenter.y - backgroundSize.height / 2f,
-            right = shapeAndBackgroundCenter.x + backgroundSize.width / 2f,
-            bottom = shapeAndBackgroundCenter.y + backgroundSize.height / 2f,
-        )
-
-    // Assert that the checked area is fully enclosed in the bitmap
-    assert(backgroundBounds.top >= 0.0f) { "background is out of bounds (top side)" }
-    assert(backgroundBounds.right <= width) { "background is out of bounds (right side)" }
-    assert(backgroundBounds.bottom <= height) { "background is out of bounds (bottom side)" }
-    assert(backgroundBounds.left >= 0.0f) { "background is out of bounds (left side)" }
-
-    // Convert the shapes into a paths
-    val foregroundPath = shape.asPath(foregroundBounds, density)
-    val backgroundPath = backgroundShape.asPath(backgroundBounds, density)
-
-    for (y in backgroundBounds.top until backgroundBounds.bottom) {
-        for (x in backgroundBounds.left until backgroundBounds.right) {
-            val point = Offset(x.toFloat(), y.toFloat())
-            val pointFarther =
-                pointFartherFromAnchor(point, shapeAndBackgroundCenter, antiAliasingGap)
-            val pointCloser = pointCloserToAnchor(point, shapeAndBackgroundCenter, antiAliasingGap)
-            if (!backgroundPath.contains(pointFarther)) {
-                continue
-            }
-            val isInside = foregroundPath.contains(pointFarther)
-            val isOutside = !foregroundPath.contains(pointCloser)
-            if (isInside) {
-                pixels.assertPixelColor(shapeColor, x, y)
-            } else if (isOutside) {
-                pixels.assertPixelColor(backgroundColor, x, y)
-            }
-        }
-    }
-}
-
-/**
  * Asserts that the given [shape] is drawn in the bitmap in the color [shapeColor] on a background
  * of [backgroundColor].
  *
@@ -255,22 +148,142 @@
     backgroundColor: Color,
     shapeColor: Color,
     shape: Shape = RectangleShape,
-    antiAliasingGap: Float = 1.0f
-) {
-    val shapeSize =
-        Size(
-            width - with(density) { horizontalPadding.toPx() * 2 },
-            height - with(density) { verticalPadding.toPx() * 2 }
-        )
-    return assertShape(
+    antiAliasingGap: Float = with(density) { 1.dp.toPx() }
+) =
+    assertShape(
         density = density,
         shape = shape,
         shapeColor = shapeColor,
         backgroundColor = backgroundColor,
         backgroundShape = RectangleShape,
-        shapeSize = shapeSize,
+        shapeSize =
+            Size(
+                width - with(density) { horizontalPadding.toPx() * 2 },
+                height - with(density) { verticalPadding.toPx() * 2 }
+            ),
         antiAliasingGap = antiAliasingGap
     )
+
+/**
+ * Asserts that the given [shape] and [backgroundShape] are drawn in the bitmap according to the
+ * given parameters.
+ *
+ * The [shape]'s bounding box should have size [shapeSize] and be centered at [shapeCenter]. The
+ * [backgroundShape]'s bounding box should have size [backgroundSize] and be centered at
+ * [backgroundCenter].
+ *
+ * Pixels that are outside the background area are not checked. Pixels that are outside the [shape]
+ * and inside the [backgroundShape] must be [backgroundColor]. Pixels that are inside the [shape]
+ * must be [shapeColor].
+ *
+ * If [backgroundColor] is `null`, only pixels inside the [shape] are checked.
+ *
+ * Because pixels on the edge of a shape are anti-aliased, pixels that are close the shape's edges
+ * are not checked. Use [antiAliasingGap] to ignore more (or less) pixels around the shape's edges.
+ * A larger [antiAliasingGap] means more pixels are left unchecked, and a gap of 0 pixels means all
+ * pixels are tested.
+ */
+// TODO (mount, malkov) : to investigate why it flakes when shape is not rect
+fun ImageBitmap.assertShape(
+    density: Density,
+    shape: Shape,
+    shapeColor: Color,
+    shapeSize: Size = Size(width.toFloat(), height.toFloat()),
+    shapeCenter: Offset = Offset(width / 2f, height / 2f),
+    backgroundShape: Shape = RectangleShape,
+    backgroundColor: Color?,
+    backgroundSize: Size = Size(width.toFloat(), height.toFloat()),
+    backgroundCenter: Offset = Offset(width / 2f, height / 2f),
+    antiAliasingGap: Float = with(density) { 1.dp.toPx() }
+) {
+    val pixels = toPixelMap()
+
+    // the bounding box of the foreground shape in the bitmap
+    val shapeBounds =
+        Rect(
+            left = shapeCenter.x - shapeSize.width / 2f,
+            top = shapeCenter.y - shapeSize.height / 2f,
+            right = shapeCenter.x + shapeSize.width / 2f,
+            bottom = shapeCenter.y + shapeSize.height / 2f,
+        )
+    // the bounding box of the background shape in the bitmap
+    val backgroundBounds =
+        Rect(
+            left = backgroundCenter.x - backgroundSize.width / 2f,
+            top = backgroundCenter.y - backgroundSize.height / 2f,
+            right = backgroundCenter.x + backgroundSize.width / 2f,
+            bottom = backgroundCenter.y + backgroundSize.height / 2f,
+        )
+
+    // Convert the shapes into a paths
+    val foregroundPath = shape.asPath(shapeBounds, density)
+    val backgroundPath = backgroundShape.asPath(backgroundBounds, density)
+
+    forEachPixelIn(shapeBounds, backgroundBounds, antiAliasingGap) { x, y, inFgBounds, inBgBounds ->
+        if (inFgBounds && !inBgBounds) {
+            // Only consider the foreground shape
+            if (foregroundPath.contains(x, y, shapeCenter, antiAliasingGap)) {
+                pixels.assertPixelColor(shapeColor, x, y)
+            }
+        } else if (inBgBounds && !inFgBounds) {
+            // Only consider the background shape, if there is one
+            if (
+                backgroundColor != null &&
+                    backgroundPath.contains(x, y, backgroundCenter, antiAliasingGap)
+            ) {
+                pixels.assertPixelColor(backgroundColor, x, y)
+            }
+        } else if (inFgBounds /* && inBgBounds */) {
+            // Need to consider both the foreground and background (if there is one)
+            if (foregroundPath.contains(x, y, shapeCenter, antiAliasingGap)) {
+                pixels.assertPixelColor(shapeColor, x, y)
+            } else if (
+                backgroundColor != null &&
+                    foregroundPath.notContains(x, y, shapeCenter, antiAliasingGap) &&
+                    backgroundPath.contains(x, y, backgroundCenter, antiAliasingGap)
+            ) {
+                pixels.assertPixelColor(backgroundColor, x, y)
+            }
+        }
+    }
+}
+
+private fun ImageBitmap.forEachPixelIn(
+    shapeBounds: Rect,
+    backgroundBounds: Rect,
+    margin: Float,
+    block: (x: Int, y: Int, inShapeBounds: Boolean, inBackgroundBounds: Boolean) -> Unit
+) {
+    // Iterate over all pixels in the background. Usually that's all we have to do.
+    for (y in rowIndices(backgroundBounds)) {
+        for (x in columnIndices(backgroundBounds)) {
+            block.invoke(x, y, shapeBounds.contains(x, y, margin), true)
+        }
+    }
+    // While checking the background area, we potentially checked a lot of foreground area at the
+    // same time. Try to avoid checking pixels again.
+    val shapeBoundsToCheck = shapeBounds.subtract(backgroundBounds)
+    for (y in rowIndices(shapeBoundsToCheck)) {
+        for (x in columnIndices(shapeBoundsToCheck)) {
+            // Anything contained in the background is already checked
+            if (!backgroundBounds.contains(x, y, margin)) {
+                block.invoke(x, y, true, false)
+            }
+        }
+    }
+}
+
+private fun ImageBitmap.rowIndices(bounds: Rect): IntRange =
+    bounds.top.coerceAtLeast(0f) until bounds.bottom.coerceAtMost(height.toFloat())
+
+private fun ImageBitmap.columnIndices(bounds: Rect): IntRange =
+    bounds.left.coerceAtLeast(0f) until bounds.right.coerceAtMost(width.toFloat())
+
+private infix fun Float.until(until: Float): IntRange {
+    val from = this.roundToInt()
+    val to = until.roundToInt()
+    if (from <= Int.MIN_VALUE) return IntRange.EMPTY
+    return from until to
 }
 
 private fun Shape.asPath(bounds: Rect, density: Density): Path {
@@ -281,41 +294,102 @@
     }
 }
 
-private infix fun Float.until(until: Float): IntRange {
-    val from = this.roundToInt()
-    val to = until.roundToInt()
-    if (from <= Int.MIN_VALUE) return IntRange.EMPTY
-    return from until to
+private fun Path.contains(x: Int, y: Int, center: Offset, margin: Float): Boolean =
+    contains(pointFartherFromAnchor(x, y, center, margin))
+
+private fun Path.notContains(x: Int, y: Int, center: Offset, margin: Float): Boolean =
+    !contains(pointCloserToAnchor(x, y, center, margin))
+
+/**
+ * Tests to see if the given point is within the path. (That is, whether the point would be in the
+ * visible portion of the path if the path was used with [Canvas.clipPath].)
+ *
+ * The `point` argument is interpreted as an offset from the origin.
+ *
+ * Returns true if the point is in the path, and false otherwise.
+ */
+@VisibleForTesting
+internal fun Path.contains(offset: Offset): Boolean {
+    val path = android.graphics.Path()
+    path.addRect(
+        /* left = */ offset.x - 0.01f,
+        /* top = */ offset.y - 0.01f,
+        /* right = */ offset.x + 0.01f,
+        /* bottom = */ offset.y + 0.01f,
+        /* dir = */ android.graphics.Path.Direction.CW
+    )
+    if (path.op(asAndroidPath(), android.graphics.Path.Op.INTERSECT)) {
+        return !path.isEmpty
+    }
+    return false
 }
 
-private fun pointCloserToAnchor(point: Offset, anchor: Offset, delta: Float): Offset {
-    val x =
+private fun pointCloserToAnchor(x: Int, y: Int, anchor: Offset, delta: Float): Offset {
+    val xx =
         when {
-            point.x > anchor.x -> max(point.x - delta, anchor.x)
-            point.x < anchor.x -> min(point.x + delta, anchor.x)
-            else -> point.x
+            x > anchor.x -> max(x - delta, anchor.x)
+            x < anchor.x -> min(x + delta, anchor.x)
+            else -> x.toFloat()
         }
-    val y =
+    val yy =
         when {
-            point.y > anchor.y -> max(point.y - delta, anchor.y)
-            point.y < anchor.y -> min(point.y + delta, anchor.y)
-            else -> point.y
+            y > anchor.y -> max(y - delta, anchor.y)
+            y < anchor.y -> min(y + delta, anchor.y)
+            else -> y.toFloat()
         }
-    return Offset(x, y)
+    return Offset(xx, yy)
 }
 
-private fun pointFartherFromAnchor(point: Offset, anchor: Offset, delta: Float): Offset {
-    val x =
+private fun pointFartherFromAnchor(x: Int, y: Int, anchor: Offset, delta: Float): Offset {
+    val xx =
         when {
-            point.x > anchor.x -> point.x + delta
-            point.x < anchor.x -> point.x - delta
-            else -> point.x
+            x > anchor.x -> x + delta
+            x < anchor.x -> x - delta
+            else -> x.toFloat()
         }
-    val y =
+    val yy =
         when {
-            point.y > anchor.y -> point.y + delta
-            point.y < anchor.y -> point.y - delta
-            else -> point.y
+            y > anchor.y -> y + delta
+            y < anchor.y -> y - delta
+            else -> y.toFloat()
         }
-    return Offset(x, y)
+    return Offset(xx, yy)
+}
+
+private fun Rect.contains(x: Int, y: Int, margin: Float): Boolean =
+    left <= x + margin && top <= y + margin && right >= x - margin && bottom >= y - margin
+
+private fun Rect.subtract(other: Rect): Rect {
+    // Subtraction can only happen if the other rect is overlapping entirely in a dimension
+    if (other.left <= this.left && other.right >= this.right) {
+        // Other rect potentially overlaps over entire width
+        return if (other.top <= this.top && other.bottom >= this.bottom) {
+            // Subtract everything
+            Rect.Zero
+        } else if (other.top <= this.top && other.bottom > this.top) {
+            // Subtract from the top
+            Rect(this.left, other.bottom, this.right, this.bottom)
+        } else if (other.top < this.bottom && other.bottom >= this.bottom) {
+            // Subtract from the bottom
+            Rect(this.left, this.top, this.right, other.top)
+        } else {
+            // Subtract nothing
+            this
+        }
+    } else if (other.top <= this.top && other.bottom >= this.bottom) {
+        // Other rect potentially overlaps over entire height
+        return if (other.left <= this.left && other.right > this.left) {
+            // Subtract from the left
+            Rect(other.right, this.top, this.right, this.bottom)
+        } else if (other.left < this.right && other.right >= this.right) {
+            // Subtract from the right
+            Rect(this.left, this.top, other.left, this.bottom)
+        } else {
+            // Subtract nothing
+            this
+        }
+    } else {
+        // Subtract nothing
+        return this
+    }
 }
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
index 074a435..82e26eb 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
@@ -1313,8 +1313,8 @@
         }
     }
 
-    // Experimentally verified that middle and start ellipsis don't work correctly on API 21
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1)
+    // Experimentally verified that start ellipsis doesn't work same way on API 22
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
     @Test
     fun testEllipsis_withMaxLinesOne_doesStartEllipsis() {
         with(defaultDensity) {
@@ -1336,7 +1336,7 @@
         }
     }
 
-    // Experimentally verified that middle and start ellipsis don't work correctly on API 21
+    // Experimentally verified that middle ellipsis doesn't work same way on API 21
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1)
     @Test
     fun testEllipsis_withMaxLinesOne_doesMiddleEllipsis() {
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
index e12b98e..9359de6 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
@@ -15,6 +15,7 @@
  */
 package androidx.compose.ui.text
 
+import android.os.Build
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
@@ -2935,6 +2936,52 @@
     }
 
     @Test
+    fun getLineStartEllipsisCount() {
+        val text = "aaaaabbbbbccccc"
+        val paragraph =
+            simpleParagraph(
+                text = text,
+                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 10.sp),
+                maxLines = 1,
+                overflow = TextOverflow.StartEllipsis,
+                width = 50f
+            )
+
+        assertThat(paragraph.lineCount).isEqualTo(1)
+
+        assertThat(paragraph.isLineEllipsized(0)).isTrue()
+        assertThat(paragraph.getLineStart(0)).isEqualTo(0)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(text.length)
+        } else {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun getLineMiddleEllipsisCount() {
+        val text = "aaaaabbbbbccccc"
+        val paragraph =
+            simpleParagraph(
+                text = text,
+                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 10.sp),
+                maxLines = 1,
+                overflow = TextOverflow.MiddleEllipsis,
+                width = 50f
+            )
+
+        assertThat(paragraph.lineCount).isEqualTo(1)
+
+        assertThat(paragraph.isLineEllipsized(0)).isTrue()
+        assertThat(paragraph.getLineStart(0)).isEqualTo(0)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(text.length)
+        } else {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(5)
+        }
+    }
+
+    @Test
     fun lineHeight_inSp() {
         val text = "abcdefgh"
         val fontSize = 20f
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt
index 17a6a87..5af082b 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.text
 
+import android.os.Build
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
@@ -31,6 +32,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
@@ -96,6 +98,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M) // b/364169257 for details
     fun width_shouldMatter_ifSoftwrapIsDisabled_butOverflowIsStartEllipsis() {
         val textLayoutResult =
             layoutText(
@@ -111,6 +114,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M) // b/364169257 for details
     fun width_shouldMatter_ifSoftwrapIsDisabled_butOverflowIsMiddleEllipsis() {
         val textLayoutResult =
             layoutText(
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
index 273ff95..0814b1a 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
@@ -48,6 +48,47 @@
     }
 
     @Test
+    fun singleLine_withEllipsisStart() {
+        val text = "abcdefghij"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 4,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.START
+            )
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(10)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(4)
+        }
+    }
+
+    @Test
+    fun singleLine_withEllipsisMiddle() {
+        val text = "abcdefghij"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 4,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.MIDDLE
+            )
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(10)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(4)
+        }
+    }
+
+    @Test
     fun excludesLineBreak_whenMaxLinesPresent_withoutEllipsis() {
         val text = "abc\ndef"
         val textSize = 20.0f
@@ -81,6 +122,40 @@
     }
 
     @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withEllipsisStart() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 10,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.START
+            )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withEllipsisMiddle() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 10,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.MIDDLE
+            )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
     fun excludesWhitespace_singleLineContent_withEllipsis() {
         val text = "abc def ghi"
         val textSize = 20.0f
@@ -166,7 +241,7 @@
             )
 
         assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
-        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(2) // ellipsis character
+        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(1)
     }
 
     private fun simpleLayout(
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
index 87c2aed..81d7f19 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
@@ -119,7 +119,7 @@
     width: Float,
     val textPaint: TextPaint,
     @TextLayoutAlignment alignment: Int = DEFAULT_ALIGNMENT,
-    ellipsize: TextUtils.TruncateAt? = null,
+    private val ellipsize: TextUtils.TruncateAt? = null,
     @TextDirection textDirectionHeuristic: Int = DEFAULT_TEXT_DIRECTION,
     lineSpacingMultiplier: Float = DEFAULT_LINESPACING_MULTIPLIER,
     @Px lineSpacingExtra: Float = DEFAULT_LINESPACING_EXTRA,
@@ -457,13 +457,13 @@
      * returns the length of the text.
      */
     fun getLineEnd(lineIndex: Int): Int =
-        if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
-            layout.getLineEnd(lineIndex)
-        } else {
+        if (layout.isLineEllipsized(lineIndex) && ellipsize == TextUtils.TruncateAt.END) {
             // Layout#getLineEnd usually gets the end of text for the last line even if ellipsis
             // happens. However, if LF character is included in the ellipsized region, getLineEnd
             // returns LF character offset. So, use end of text for line end here.
             layout.text.length
+        } else {
+            layout.getLineEnd(lineIndex)
         }
 
     /**
@@ -471,10 +471,10 @@
      * whitespaces are not counted as visible characters.
      */
     fun getLineVisibleEnd(lineIndex: Int): Int =
-        if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
-            layoutHelper.getLineVisibleEnd(lineIndex)
-        } else {
+        if (layout.isLineEllipsized(lineIndex) && ellipsize == TextUtils.TruncateAt.END) {
             layout.getLineStart(lineIndex) + layout.getEllipsisStart(lineIndex)
+        } else {
+            layoutHelper.getLineVisibleEnd(lineIndex)
         }
 
     fun isLineEllipsized(lineIndex: Int) = layout.isLineEllipsized(lineIndex)
diff --git a/compose/ui/ui-util/api/current.txt b/compose/ui/ui-util/api/current.txt
index 57823b5..e18cbf3 100644
--- a/compose/ui/ui-util/api/current.txt
+++ b/compose/ui/ui-util/api/current.txt
@@ -54,6 +54,7 @@
     method public static inline <T, R> java.util.List<R> fastMapNotNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R?> transform);
     method public static inline <T, R, C extends java.util.Collection<? super R>> C fastMapTo(java.util.List<? extends T>, C destination, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMaxBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
+    method public static inline <T, R extends java.lang.Comparable<? super R>> R fastMaxOfOrDefault(java.util.List<? extends T>, R defaultValue, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <T, R extends java.lang.Comparable<? super R>> R? fastMaxOfOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMinByOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <S, T extends S> S fastReduce(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super S,? super T,? extends S> operation);
diff --git a/compose/ui/ui-util/api/restricted_current.txt b/compose/ui/ui-util/api/restricted_current.txt
index 57823b5..be90ec4 100644
--- a/compose/ui/ui-util/api/restricted_current.txt
+++ b/compose/ui/ui-util/api/restricted_current.txt
@@ -54,12 +54,15 @@
     method public static inline <T, R> java.util.List<R> fastMapNotNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R?> transform);
     method public static inline <T, R, C extends java.util.Collection<? super R>> C fastMapTo(java.util.List<? extends T>, C destination, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMaxBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
+    method public static inline <T, R extends java.lang.Comparable<? super R>> R fastMaxOfOrDefault(java.util.List<? extends T>, R defaultValue, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <T, R extends java.lang.Comparable<? super R>> R? fastMaxOfOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMinByOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
     method public static inline <S, T extends S> S fastReduce(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super S,? super T,? extends S> operation);
     method public static inline <T> int fastSumBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,java.lang.Integer> selector);
     method public static inline <T, R, V> java.util.List<V> fastZip(java.util.List<? extends T>, java.util.List<? extends R> other, kotlin.jvm.functions.Function2<? super T,? super R,? extends V> transform);
     method public static inline <T, R> java.util.List<R> fastZipWithNext(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> transform);
+    method @kotlin.PublishedApi internal static Void throwNoSuchElementException(String message);
+    method @kotlin.PublishedApi internal static void throwUnsupportedOperationException(String message);
   }
 
   public final class MathHelpersKt {
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index 9ea3f32..2ce5e6f 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -42,6 +42,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlib)
+                implementation("androidx.collection:collection:1.4.3")
             }
         }
 
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
index 80d197e..889ba5c 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.util
 
+import androidx.collection.MutableScatterSet
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 
@@ -307,6 +308,30 @@
 }
 
 /**
+ * Returns the largest value among all values produced by selector function applied to each element
+ * in the collection or [defaultValue] if there are no elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn") // Treat Kotlin Contracts as non-experimental.
+@OptIn(ExperimentalContracts::class)
+inline fun <T, R : Comparable<R>> List<T>.fastMaxOfOrDefault(
+    defaultValue: R,
+    selector: (T) -> R
+): R {
+    contract { callsInPlace(selector) }
+    if (isEmpty()) return defaultValue
+    var maxValue = selector(get(0))
+    for (i in 1..lastIndex) {
+        val v = selector(get(i))
+        if (v > maxValue) maxValue = v
+    }
+    return maxValue
+}
+
+/**
  * Returns a list containing the results of applying the given [transform] function to each pair of
  * two adjacent elements in this collection.
  *
@@ -320,7 +345,7 @@
 @OptIn(ExperimentalContracts::class)
 inline fun <T, R> List<T>.fastZipWithNext(transform: (T, T) -> R): List<R> {
     contract { callsInPlace(transform) }
-    if (size == 0 || size == 1) return emptyList()
+    if (size <= 1) return emptyList()
     val result = mutableListOf<R>()
     var current = get(0)
     // `until` as we don't want to invoke this for the last element, since that won't have a `next`
@@ -351,7 +376,7 @@
 @OptIn(ExperimentalContracts::class)
 inline fun <S, T : S> List<T>.fastReduce(operation: (acc: S, T) -> S): S {
     contract { callsInPlace(operation) }
-    if (isEmpty()) throw UnsupportedOperationException("Empty collection can't be reduced.")
+    if (isEmpty()) throwUnsupportedOperationException("Empty collection can't be reduced.")
     var accumulator: S = first()
     for (i in 1..lastIndex) {
         accumulator = operation(accumulator, get(i))
@@ -435,7 +460,7 @@
 @OptIn(ExperimentalContracts::class)
 inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
     contract { callsInPlace(selector) }
-    val set = HashSet<K>(size)
+    val set = MutableScatterSet<K>(size)
     val target = ArrayList<T>(size)
     fastForEach { e ->
         val key = selector(e)
@@ -517,7 +542,7 @@
 inline fun <T> List<T>.fastFirst(predicate: (T) -> Boolean): T {
     contract { callsInPlace(predicate) }
     fastForEach { if (predicate(it)) return it }
-    throw NoSuchElementException("Collection contains no element matching the predicate.")
+    throwNoSuchElementException("Collection contains no element matching the predicate.")
 }
 
 /**
@@ -564,3 +589,13 @@
         else -> append(element.toString())
     }
 }
+
+@PublishedApi
+internal fun throwNoSuchElementException(message: String): Nothing {
+    throw NoSuchElementException(message)
+}
+
+@PublishedApi
+internal fun throwUnsupportedOperationException(message: String) {
+    throw UnsupportedOperationException(message)
+}
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 8ed523d..799e1ad 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2514,15 +2514,18 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScaleFactor {
+    ctor public ScaleFactor(long packedValue);
     method @androidx.compose.runtime.Stable public inline operator float component1();
     method @androidx.compose.runtime.Stable public inline operator float component2();
     method public long copy(optional float scaleX, optional float scaleY);
     method @androidx.compose.runtime.Stable public operator long div(float operand);
-    method public float getScaleX();
-    method public float getScaleY();
+    method public long getPackedValue();
+    method public inline float getScaleX();
+    method public inline float getScaleY();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property @androidx.compose.runtime.Stable public final float scaleX;
-    property @androidx.compose.runtime.Stable public final float scaleY;
+    property public final long packedValue;
+    property @androidx.compose.runtime.Stable public final inline float scaleX;
+    property @androidx.compose.runtime.Stable public final inline float scaleY;
     field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
   }
 
@@ -2532,7 +2535,7 @@
   }
 
   public final class ScaleFactorKt {
-    method @androidx.compose.runtime.Stable public static long ScaleFactor(float scaleX, float scaleY);
+    method @androidx.compose.runtime.Stable public static inline long ScaleFactor(float scaleX, float scaleY);
     method @androidx.compose.runtime.Stable public static operator long div(long, long scaleFactor);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 59af42d..e675339 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2521,15 +2521,18 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScaleFactor {
+    ctor public ScaleFactor(long packedValue);
     method @androidx.compose.runtime.Stable public inline operator float component1();
     method @androidx.compose.runtime.Stable public inline operator float component2();
     method public long copy(optional float scaleX, optional float scaleY);
     method @androidx.compose.runtime.Stable public operator long div(float operand);
-    method public float getScaleX();
-    method public float getScaleY();
+    method public long getPackedValue();
+    method public inline float getScaleX();
+    method public inline float getScaleY();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property @androidx.compose.runtime.Stable public final float scaleX;
-    property @androidx.compose.runtime.Stable public final float scaleY;
+    property public final long packedValue;
+    property @androidx.compose.runtime.Stable public final inline float scaleX;
+    property @androidx.compose.runtime.Stable public final inline float scaleY;
     field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
   }
 
@@ -2539,7 +2542,7 @@
   }
 
   public final class ScaleFactorKt {
-    method @androidx.compose.runtime.Stable public static long ScaleFactor(float scaleX, float scaleY);
+    method @androidx.compose.runtime.Stable public static inline long ScaleFactor(float scaleX, float scaleY);
     method @androidx.compose.runtime.Stable public static operator long div(long, long scaleFactor);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
index bfeca00..b208f50 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
@@ -1548,6 +1549,52 @@
             assertThat(postFlingCount).isGreaterThan(0)
         }
     }
+
+    @Test
+    fun modifierIsRemoved_shouldKeepInfoAboutPreviousParent() {
+        val innerDispatcher = NestedScrollDispatcher()
+        val outerDispatcher = NestedScrollDispatcher()
+        var keepAround by mutableStateOf(true)
+
+        rule.setContent {
+            Column(
+                modifier =
+                    Modifier.nestedScroll(
+                        dispatcher = outerDispatcher,
+                        connection = object : NestedScrollConnection {}
+                    )
+            ) {
+                Box(
+                    Modifier.size(400.dp)
+                        .then(
+                            if (keepAround)
+                                Modifier.nestedScroll(
+                                    dispatcher = innerDispatcher,
+                                    connection = object : NestedScrollConnection {}
+                                )
+                            else Modifier
+                        )
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(innerDispatcher.lastKnownValidParentNode).isNull()
+            assertThat(innerDispatcher.nestedScrollNode?.parentNestedScrollNode)
+                .isEqualTo(outerDispatcher.nestedScrollNode)
+        }
+
+        rule.runOnIdle {
+            keepAround = false // remove inner node
+        }
+
+        rule.runOnIdle {
+            // the inner node's parent is the outer node
+            assertThat(innerDispatcher.lastKnownValidParentNode)
+                .isEqualTo(outerDispatcher.nestedScrollNode)
+            assertThat(innerDispatcher.nestedScrollNode?.parentNestedScrollNode).isNull()
+        }
+    }
 }
 
 private fun Offset.customEquals(other: Offset): Boolean {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 38f3d5c..7f5532a 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -76,6 +76,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -3105,6 +3106,34 @@
         }
 
     @Test
+    fun testApproachUsingContentNotComposedInLookahead() {
+        var items by mutableStateOf(List(500) { it })
+        rule.setContent {
+            LookaheadScope {
+                LazyColumn(Modifier.requiredHeight(400.dp)) {
+                    items(items, key = { it }) {
+                        Box(
+                            Modifier.animateItem(
+                                fadeInSpec = null,
+                                placementSpec = tween(15),
+                                fadeOutSpec = tween(15)
+                            )
+                        ) {
+                            Box { Text("Item $it") }
+                        }
+                    }
+                }
+            }
+        }
+
+        repeat(30) {
+            rule.waitForIdle()
+            items = items.shuffled()
+            Snapshot.sendApplyNotifications()
+        }
+    }
+
+    @Test
     fun testLookaheadAndApproachCoordinatesAreConsistentOnFirstPass_usingAlign() =
         with(rule.density) {
             val rootSizePx = 300
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
index 28fc17c..fd37d97 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
@@ -18,17 +18,32 @@
 
 import android.view.View
 import android.view.inputmethod.BaseInputConnection
+import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.InputConnection
+import android.widget.EditText
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.input.pointer.MatrixPositionCalculator
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.PlatformTextInputModifierNode
 import androidx.compose.ui.platform.establishTextInputSession
+import androidx.compose.ui.platform.platformTextInputServiceInterceptor
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -623,6 +638,79 @@
         }
     }
 
+    @Suppress("DEPRECATION")
+    @Test
+    fun focusSwitchToAndroidViewNonEditor_hidesKeyboard() {
+        lateinit var button: android.widget.Button
+        val inputMethodManager = TestInputMethodManager()
+        platformTextInputServiceInterceptor = { original ->
+            TextInputServiceAndroid(
+                view = (original as TextInputServiceAndroid).view,
+                rootPositionCalculator = FakeMatrixPositionCalculator,
+                inputMethodManager = inputMethodManager
+            )
+        }
+        rule.setContent {
+            Column {
+                Box(TestElement { node1 = it }.focusable().testTag("tag"))
+                AndroidView(
+                    factory = { context ->
+                        android.widget.Button(context).also {
+                            it.isFocusableInTouchMode = true
+                            button = it
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag("tag").requestFocus()
+        rule.waitForIdle()
+
+        inputMethodManager.reset()
+
+        // fake TextField is active, a session is started. Now let's request focus on Button
+        rule.runOnUiThread { button.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(inputMethodManager.restartCalls).isEqualTo(1)
+            assertThat(inputMethodManager.hideKeyboardCalls).isEqualTo(1)
+        }
+    }
+
+    @Suppress("DEPRECATION")
+    @Test
+    fun focusSwitchToAndroidViewEditor_doesNotHideKeyboard() {
+        lateinit var editText: EditText
+        val inputMethodManager = TestInputMethodManager()
+        platformTextInputServiceInterceptor = { original ->
+            TextInputServiceAndroid(
+                view = (original as TextInputServiceAndroid).view,
+                rootPositionCalculator = FakeMatrixPositionCalculator,
+                inputMethodManager = inputMethodManager
+            )
+        }
+        rule.setContent {
+            Column {
+                Box(TestElement { node1 = it }.focusable().testTag("tag"))
+                AndroidView(factory = { EditText(it).also { editText = it } })
+            }
+        }
+
+        rule.onNodeWithTag("tag").requestFocus()
+
+        rule.waitForIdle()
+        inputMethodManager.reset()
+
+        // fake TextField is active, a session is started. Now let's request focus on EditText
+        rule.runOnUiThread { editText.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+            assertThat(inputMethodManager.hideKeyboardCalls).isEqualTo(0)
+        }
+    }
+
     private fun setupContent() {
         rule.setContent {
             hostView = LocalView.current as AndroidComposeView
@@ -643,10 +731,89 @@
     }
 
     private class TestNode(var onNode: (PlatformTextInputModifierNode) -> Unit) :
-        Modifier.Node(), PlatformTextInputModifierNode {
+        Modifier.Node(), PlatformTextInputModifierNode, FocusEventModifierNode {
+
+        private var inputSessionJob: Job? = null
+
+        private fun startInputSession() {
+            inputSessionJob =
+                coroutineScope.launch {
+                    establishTextInputSession {
+                        launch {
+                            startInputMethod(
+                                object : TestInputMethodRequest(view) {
+                                    override fun createInputConnection(
+                                        outAttributes: EditorInfo
+                                    ): InputConnection = BaseInputConnection(view, true)
+                                }
+                            )
+                        }
+                        awaitCancellation()
+                    }
+                }
+        }
+
+        private fun disposeInputSession() {
+            inputSessionJob?.cancel()
+            inputSessionJob = null
+        }
 
         override fun onAttach() {
             onNode(this)
         }
+
+        override fun onFocusEvent(focusState: FocusState) {
+            if (focusState.isFocused) {
+                startInputSession()
+            } else {
+                disposeInputSession()
+            }
+        }
     }
 }
+
+private object FakeMatrixPositionCalculator : MatrixPositionCalculator {
+    override fun localToScreen(localTransform: Matrix) = Unit
+
+    override fun localToScreen(localPosition: Offset) = localPosition
+
+    override fun screenToLocal(positionOnScreen: Offset) = positionOnScreen
+}
+
+@Suppress("DEPRECATION")
+private class TestInputMethodManager : InputMethodManager {
+    var restartCalls = 0
+    var showKeyboardCalls = 0
+    var hideKeyboardCalls = 0
+
+    fun reset() {
+        restartCalls = 0
+        showKeyboardCalls = 0
+        hideKeyboardCalls = 0
+    }
+
+    override fun isActive(): Boolean = true
+
+    override fun restartInput() {
+        restartCalls++
+    }
+
+    override fun showSoftInput() {
+        showKeyboardCalls++
+    }
+
+    override fun hideSoftInput() {
+        hideKeyboardCalls++
+    }
+
+    override fun updateExtractedText(token: Int, extractedText: ExtractedText) {}
+
+    override fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    ) {}
+
+    override fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {}
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index edb02b4..14c0c52 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -270,6 +270,19 @@
     }
 
     private fun processInputCommands() {
+        // If the associated view is not focused anymore, we should check whether the focus has
+        // transitioned into another Editor.
+        if (!view.isFocused) {
+            val focusedView = view.rootView.findFocus()
+            // If a view is focused and is an editor, we can skip the queued up commands since the
+            // new editor is going to manage the keyboard and the input session. Otherwise we should
+            // process the queue since it probably contains StopInput or HideKeyboard calls to
+            // clean up after us.
+            if (focusedView?.onCheckIsTextEditor() == true) {
+                textInputCommandQueue.clear()
+                return
+            }
+        }
         // Multiple commands may have been queued up in the channel while this function was
         // waiting to be resumed. We don't execute the commands as they come in because making a
         // bunch of calls to change the actual IME quickly can result in flickers. Instead, we
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index 0e07822..6c10ef5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -60,7 +60,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastMaxBy
 import androidx.core.graphics.Insets
 import androidx.core.view.OnApplyWindowInsetsListener
 import androidx.core.view.ViewCompat
@@ -74,6 +73,7 @@
 import androidx.savedstate.findViewTreeSavedStateRegistryOwner
 import androidx.savedstate.setViewTreeSavedStateRegistryOwner
 import java.util.UUID
+import kotlin.math.max
 import kotlin.math.roundToInt
 
 /**
@@ -562,9 +562,19 @@
 @Composable
 private fun DialogLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
     Layout(content = content, modifier = modifier) { measurables, constraints ->
-        val placeables = measurables.fastMap { it.measure(constraints) }
-        val width = placeables.fastMaxBy { it.width }?.width ?: constraints.minWidth
-        val height = placeables.fastMaxBy { it.height }?.height ?: constraints.minHeight
-        layout(width, height) { placeables.fastForEach { it.placeRelative(0, 0) } }
+        var maxWidth = 0
+        var maxHeight = 0
+        val placeables =
+            measurables.fastMap {
+                it.measure(constraints).apply {
+                    maxWidth = max(maxWidth, width)
+                    maxHeight = max(maxHeight, height)
+                }
+            }
+        if (measurables.isEmpty()) {
+            maxWidth = constraints.minWidth
+            maxHeight = constraints.minHeight
+        }
+        layout(maxWidth, maxHeight) { placeables.fastForEach { it.placeRelative(0, 0) } }
     }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 35431d0..cc18d62 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -85,6 +85,7 @@
 import androidx.savedstate.findViewTreeSavedStateRegistryOwner
 import androidx.savedstate.setViewTreeSavedStateRegistryOwner
 import java.util.UUID
+import kotlin.math.max
 import kotlinx.coroutines.isActive
 import org.jetbrains.annotations.TestOnly
 
@@ -436,14 +437,15 @@
                 layout(p.width, p.height) { p.placeRelative(0, 0) }
             }
             else -> {
-                val placeables = measurables.fastMap { it.measure(constraints) }
                 var width = 0
                 var height = 0
-                for (i in 0..placeables.lastIndex) {
-                    val p = placeables[i]
-                    width = maxOf(width, p.width)
-                    height = maxOf(height, p.height)
-                }
+                val placeables =
+                    measurables.fastMap {
+                        it.measure(constraints).apply {
+                            width = max(width, this.width)
+                            height = max(height, this.height)
+                        }
+                    }
                 layout(width, height) {
                     for (i in 0..placeables.lastIndex) {
                         val p = placeables[i]
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
index f27a52f..4e7c151 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
@@ -40,6 +40,7 @@
 class TextInputServiceAndroidCommandDebouncingTest {
 
     private val view = mock<View>()
+    private val parentView = mock<View>()
     private val inputMethodManager = TestInputMethodManager()
     private val executor = Executor { runnable -> scope.launch { runnable.run() } }
     private val service =
@@ -54,8 +55,11 @@
 
     @Before
     fun setUp() {
-        // Default the view to focused because when it's not focused commands should be ignored.
+        // Default the view to focused. When it is not focused the commands should be processed
+        // according to whether the new focused View is an Editor.
         whenever(view.isFocused).thenReturn(true)
+        whenever(view.rootView).thenReturn(parentView)
+        whenever(parentView.findFocus()).thenReturn(null)
     }
 
     @After
@@ -249,15 +253,29 @@
     }
 
     @Test
-    fun commandsAreDrained_whenProcessedWithoutFocus() {
+    fun commandsAreNotDrained_whenProcessedWithoutFocus_and_focusDidNotSwitchToAnEditor() {
         whenever(view.isFocused).thenReturn(false)
+        val newFocusedView = mock<View>()
+        whenever(newFocusedView.onCheckIsTextEditor()).thenReturn(false)
+        whenever(parentView.findFocus()).thenReturn(newFocusedView)
         service.showSoftwareKeyboard()
-        service.hideSoftwareKeyboard()
-        scope.advanceUntilIdle()
-        whenever(view.isFocused).thenReturn(true)
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
+    }
+
+    @Test
+    fun commandsAreDrained_whenProcessedWithoutFocus_and_focusSwitchedToAnEditor() {
+        whenever(view.isFocused).thenReturn(false)
+        val newFocusedView = mock<View>()
+        whenever(newFocusedView.onCheckIsTextEditor()).thenReturn(true)
+        whenever(parentView.findFocus()).thenReturn(newFocusedView)
+        service.stopInput()
+        service.hideSoftwareKeyboard()
+        scope.advanceUntilIdle()
+
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
index b8d1e64..5e82355 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
@@ -106,6 +106,9 @@
 
     internal var nestedScrollNode: NestedScrollNode? = null
 
+    // caches last known parent for fling clean up use.
+    internal var lastKnownValidParentNode: NestedScrollNode? = null
+
     // lambda to calculate the most outer nested scroll scope for this dispatcher on demand
     internal var calculateNestedScrollScope: () -> CoroutineScope? = { scope }
 
@@ -142,7 +145,9 @@
 
     /**
      * Parent to be set when attached to nested scrolling chain. `null` is valid and means there no
-     * nested scrolling parent above
+     * nested scrolling parent above. The last known attached parent might be used in case this
+     * dispatcher is not attached to any node, that is [nestedScrollNode?.parentNestedScrollNode] is
+     * null.
      */
     internal val parent: NestedScrollConnection?
         get() = nestedScrollNode?.parentNestedScrollNode
@@ -204,7 +209,17 @@
      * @return velocity that has been consumed by all the ancestors
      */
     suspend fun dispatchPostFling(consumed: Velocity, available: Velocity): Velocity {
-        return parent?.onPostFling(consumed, available) ?: Velocity.Zero
+        // lastKnownValidParentNode can be used to send clean up signals.
+        // If this dispatcher's regular parent is not present it means either it never attached or
+        // it was detached. If it was detached we have information about its last known parent so
+        // we use it to send the post fling signal. We don't need to do the same for the other
+        // methods because the problem with parity in this API comes from a node that detaches
+        // during a fling.
+        return if (parent == null) {
+            lastKnownValidParentNode?.onPostFling(consumed, available) ?: Velocity.Zero
+        } else {
+            parent?.onPostFling(consumed, available) ?: Velocity.Zero
+        }
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
index 432daec..680033b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.findNearestAncestor
+import androidx.compose.ui.node.traverseAncestors
 import androidx.compose.ui.unit.Velocity
 import kotlinx.coroutines.CoroutineScope
 
@@ -49,6 +50,8 @@
         resolvedDispatcher = dispatcher ?: NestedScrollDispatcher() // Resolve null dispatcher
     }
 
+    internal var lastKnownValidParentNode: NestedScrollNode? = null
+
     internal val parentNestedScrollNode: NestedScrollNode?
         get() = if (isAttached) findNearestAncestor() else null
 
@@ -94,11 +97,12 @@
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-
         val selfConsumed = connection.onPostFling(consumed, available)
+        // if we receive an onPostFling after detaching this node, use the last known parent
+        // if this parent is also detached it will send the signal through the detached parents
+        val parent = if (isAttached) parentConnection else lastKnownValidParentNode
         val parentConsumed =
-            parentConnection?.onPostFling(consumed + selfConsumed, available - selfConsumed)
-                ?: Velocity.Zero
+            parent?.onPostFling(consumed + selfConsumed, available - selfConsumed) ?: Velocity.Zero
         return selfConsumed + parentConsumed
     }
 
@@ -128,6 +132,9 @@
     }
 
     override fun onDetach() {
+        // cache parent for detached clean up access in the dispatcher and in this node.
+        lastKnownValidParentNode = findNearestAttachedAncestor()
+        resolvedDispatcher.lastKnownValidParentNode = lastKnownValidParentNode
         resetDispatcherFields()
     }
 
@@ -137,6 +144,9 @@
      */
     private fun updateDispatcherFields() {
         resolvedDispatcher.nestedScrollNode = this
+        // reset lastKnownValidParentNodes
+        resolvedDispatcher.lastKnownValidParentNode = null
+        lastKnownValidParentNode = null
         resolvedDispatcher.calculateNestedScrollScope = { nestedCoroutineScope }
         resolvedDispatcher.scope = coroutineScope
     }
@@ -155,3 +165,16 @@
         updateDispatcher(dispatcher)
     }
 }
+
+private fun <T : TraversableNode> T.findNearestAttachedAncestor(): T? {
+    var node: T? = null
+    traverseAncestors {
+        if (it.node.isAttached) {
+            node = it
+            false
+        } else {
+            true
+        }
+    }
+    return node
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index e3537fd..33359fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.input.pointer
 
 import androidx.compose.runtime.Immutable
@@ -1016,7 +1018,8 @@
     val y = position.y
     val width = size.width
     val height = size.height
-    return x < 0f || x > width || y < 0f || y > height
+    // Branch-less
+    return (x < 0f) or (x > width) or (y < 0f) or (y > height)
 }
 
 /**
@@ -1027,15 +1030,24 @@
  * the pointer region.
  */
 fun PointerInputChange.isOutOfBounds(size: IntSize, extendedTouchPadding: Size): Boolean {
-    if (type != PointerType.Touch) {
-        @Suppress("DEPRECATION") return isOutOfBounds(size)
-    }
+    // Set to 1 when the pointer type is touch, 0 otherwise
+    // No-op at the CPU level
+    val isTouch = (type == PointerType.Touch).toInt()
+
     val position = position
     val x = position.x
     val y = position.y
-    val minX = -extendedTouchPadding.width
-    val maxX = size.width + extendedTouchPadding.width
-    val minY = -extendedTouchPadding.height
-    val maxY = size.height + extendedTouchPadding.height
-    return x < minX || x > maxX || y < minY || y > maxY
+
+    // Set extentX to 0 when the pointer type is *not* touch
+    val extentX = extendedTouchPadding.width * isTouch
+    val maxX = size.width + extentX
+
+    // Set extentY to 0 when the pointer type is *not* touch
+    val extentY = extendedTouchPadding.height * isTouch
+    val maxY = size.height + extentY
+
+    // Don't branch
+    return (x < -extentX) or (x > maxX) or (y < -extentY) or (y > maxY)
 }
+
+private inline fun Boolean.toInt() = if (this) 1 else 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index 49aa673..2ba31ca 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.input.pointer
 
 import androidx.collection.LongSparseArray
@@ -211,7 +213,7 @@
             if (it.down) {
                 previousPointerInputData.put(
                     it.id.value,
-                    PointerInputData(it.uptime, it.positionOnScreen, it.down, it.type)
+                    PointerInputData(it.uptime, it.positionOnScreen, it.down)
                 )
             } else {
                 previousPointerInputData.remove(it.id.value)
@@ -229,20 +231,18 @@
     private class PointerInputData(
         val uptime: Long,
         val positionOnScreen: Offset,
-        val down: Boolean,
-        val type: PointerType
+        val down: Boolean
     )
 }
 
 /** The result of a call to [PointerInputEventProcessor.process]. */
-// TODO(shepshpard): Not sure if storing these values in a int is most efficient overall.
 @kotlin.jvm.JvmInline
-internal value class ProcessResult(private val value: Int) {
+internal value class ProcessResult(val value: Int) {
     val dispatchedToAPointerInputModifier
-        get() = (value and 1) != 0
+        inline get() = (value and 0x1) != 0
 
     val anyMovementConsumed
-        get() = (value and (1 shl 1)) != 0
+        inline get() = (value and 0x2) != 0
 }
 
 /**
@@ -256,7 +256,9 @@
     dispatchedToAPointerInputModifier: Boolean,
     anyMovementConsumed: Boolean
 ): ProcessResult {
-    val val1 = if (dispatchedToAPointerInputModifier) 1 else 0
-    val val2 = if (anyMovementConsumed) (1 shl 1) else 0
-    return ProcessResult(val1 or val2)
+    return ProcessResult(
+        dispatchedToAPointerInputModifier.toInt() or (anyMovementConsumed.toInt() shl 1)
+    )
 }
+
+private inline fun Boolean.toInt() = if (this) 1 else 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/PointerIdArray.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/PointerIdArray.kt
index d664f85..d96d294 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/PointerIdArray.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/PointerIdArray.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.input.pointer.util
 
 import androidx.compose.ui.input.pointer.PointerId
@@ -25,7 +27,6 @@
  * (since LongArray is not itself resizable).
  */
 internal class PointerIdArray {
-
     /**
      * The size of this [PointerIdArray], which is equal to the number of ids stored in the array.
      */
@@ -34,6 +35,10 @@
     var size = 0
         private set
 
+    /** Returns index of last item in array */
+    inline val lastIndex: Int
+        get() = size - 1
+
     /**
      * The ids are stored as Long values in a LongArray. LongArray is not resizable, and we may need
      * to expand this array if there are many pointer ids in use at any given time, so we keep the
@@ -58,7 +63,7 @@
      *
      * @return true if [pointerId] was in the array, false otherwise
      */
-    fun remove(pointerId: PointerId): Boolean {
+    inline fun remove(pointerId: PointerId): Boolean {
         return remove(pointerId.value)
     }
 
@@ -70,8 +75,11 @@
      */
     fun remove(pointerIdValue: Long): Boolean {
         for (i in 0 until size) {
-            if (pointerIdValue == this[i].value) {
-                removeAt(i)
+            if (pointerIdValue == internalArray[i]) {
+                for (j in i until size - 1) {
+                    internalArray[j] = internalArray[j + 1]
+                }
+                size--
                 return true
             }
         }
@@ -116,7 +124,7 @@
      *
      * @return true if id was added, false otherwise
      */
-    fun add(pointerId: PointerId): Boolean {
+    inline fun add(pointerId: PointerId): Boolean {
         return add(pointerId.value)
     }
 
@@ -127,20 +135,27 @@
      * it.
      */
     operator fun set(index: Int, value: Long) {
+        var internalArray = internalArray
         if (index >= internalArray.size) {
             // Increase the size of the backing array
-            internalArray = internalArray.copyOf(maxOf(index + 1, internalArray.size * 2))
+            internalArray = resizeStorage(index + 1)
         }
         internalArray[index] = value
         if (index >= size) size = index + 1
     }
 
+    private fun resizeStorage(minSize: Int): LongArray {
+        return internalArray.copyOf(maxOf(minSize, internalArray.size * 2)).apply {
+            internalArray = this
+        }
+    }
+
     /**
      * Sets the value at the given index to [pointerId]. The index must be less than or equal to the
      * current size of the array. If it is equal to the size of the array, the storage in the array
      * will be expanded to ensure that the item can be added to the end of it.
      */
-    operator fun set(index: Int, pointerId: PointerId) {
+    inline operator fun set(index: Int, pointerId: PointerId) {
         set(index, pointerId.value)
     }
 
@@ -151,7 +166,7 @@
     }
 
     /** Returns true if [pointerId] is in the array, false otherwise */
-    fun contains(pointerId: PointerId): Boolean {
+    inline fun contains(pointerId: PointerId): Boolean {
         return contains(pointerId.value)
     }
 
@@ -162,8 +177,4 @@
         }
         return false
     }
-
-    /** Returns index of last item in array */
-    inline val lastIndex: Int
-        get() = size - 1
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/internal/InlineClassHelper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/internal/InlineClassHelper.kt
index cae8d987..18b188e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/internal/InlineClassHelper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/internal/InlineClassHelper.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.internal
 
 import kotlin.contracts.ExperimentalContracts
@@ -34,6 +36,18 @@
     throw IllegalArgumentException(message)
 }
 
+internal fun throwIllegalArgumentExceptionForNullCheck(message: String): Nothing {
+    throw IllegalArgumentException(message)
+}
+
+internal fun throwUnsupportedOperationException(message: String) {
+    throw UnsupportedOperationException(message)
+}
+
+internal fun throwIndexOutOfBoundsException(message: String) {
+    throw IndexOutOfBoundsException(message)
+}
+
 // Like Kotlin's check() but without the .toString() call and
 // a non-inline throw
 @Suppress("BanInlineOptIn")
@@ -45,7 +59,7 @@
     }
 }
 
-@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@Suppress("BanInlineOptIn")
 @OptIn(ExperimentalContracts::class)
 internal inline fun checkPrecondition(value: Boolean) {
     contract { returns() implies value }
@@ -69,7 +83,7 @@
 }
 
 // Like Kotlin's checkNotNull() but with a non-inline throw
-@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@Suppress("BanInlineOptIn")
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T : Any> checkPreconditionNotNull(value: T?): T {
     contract { returns() implies (value != null) }
@@ -90,3 +104,17 @@
         throwIllegalArgumentException(lazyMessage())
     }
 }
+
+// Like Kotlin's requireNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> requirePreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalArgumentExceptionForNullCheck(lazyMessage())
+    }
+
+    return value
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
index d64d81b..eff14bb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.layout
 
+import androidx.compose.ui.internal.requirePreconditionNotNull
+import androidx.compose.ui.internal.throwIllegalArgumentExceptionForNullCheck
 import androidx.compose.ui.node.LayoutModifierNodeCoordinator
 import androidx.compose.ui.node.NodeCoordinator
 import androidx.compose.ui.node.checkMeasuredSize
@@ -54,7 +56,7 @@
 ) : ApproachMeasureScope, MeasureScope by coordinator, LookaheadScope {
     override val lookaheadConstraints: Constraints
         get() =
-            requireNotNull(coordinator.lookaheadConstraints) {
+            requirePreconditionNotNull(coordinator.lookaheadConstraints) {
                 "Error: Lookahead constraints requested before lookahead measure."
             }
 
@@ -68,13 +70,13 @@
         if (this is NodeCoordinator) {
             return lookaheadDelegate?.lookaheadLayoutCoordinates ?: this
         }
-        throw IllegalArgumentException("Unsupported LayoutCoordinates: $this")
+        throwIllegalArgumentExceptionForNullCheck("Unsupported LayoutCoordinates")
     }
 
     override val Placeable.PlacementScope.lookaheadScopeCoordinates: LayoutCoordinates
         get() {
             val lookaheadRoot = coordinator.layoutNode.lookaheadRoot
-            requireNotNull(lookaheadRoot) {
+            requirePreconditionNotNull(lookaheadRoot) {
                 "Error: Requesting LookaheadScopeCoordinates is not permitted from outside of a" +
                     " LookaheadScope."
             }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
index d6ce074..bb72e66 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.layout
 
 import androidx.compose.runtime.Immutable
@@ -149,6 +151,8 @@
     return min(widthScale, heightScale)
 }
 
-private fun computeFillWidth(srcSize: Size, dstSize: Size): Float = dstSize.width / srcSize.width
+private inline fun computeFillWidth(srcSize: Size, dstSize: Size): Float =
+    dstSize.width / srcSize.width
 
-private fun computeFillHeight(srcSize: Size, dstSize: Size): Float = dstSize.height / srcSize.height
+private inline fun computeFillHeight(srcSize: Size, dstSize: Size): Float =
+    dstSize.height / srcSize.height
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 1bdba48..7291168 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
@@ -41,6 +41,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastForEach
 import kotlin.jvm.JvmName
 
@@ -355,8 +356,8 @@
         rulers: (RulerScope.() -> Unit)?,
         placementBlock: Placeable.PlacementScope.() -> Unit
     ): MeasureResult {
-        val w = width.coerceAtLeast(0)
-        val h = height.coerceAtLeast(0)
+        val w = width.fastCoerceAtLeast(0)
+        val h = height.fastCoerceAtLeast(0)
         checkMeasuredSize(w, h)
         return object : MeasureResult {
             override val width: Int
@@ -389,8 +390,8 @@
         rulers: (RulerScope.() -> Unit)?,
         placementBlock: Placeable.PlacementScope.() -> Unit
     ): MeasureResult {
-        val w = width.coerceAtLeast(0)
-        val h = height.coerceAtLeast(0)
+        val w = width.fastCoerceAtLeast(0)
+        val h = height.fastCoerceAtLeast(0)
         checkMeasuredSize(w, h)
         return object : MeasureResult {
             override val width: Int
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
index 0fec80b..bee65dd 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.internal.throwUnsupportedOperationException
 import androidx.compose.ui.node.NodeCoordinator
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastCoerceIn
@@ -155,7 +156,7 @@
      */
     @Suppress("DocumentExceptions")
     fun transformFrom(sourceCoordinates: LayoutCoordinates, matrix: Matrix) {
-        throw UnsupportedOperationException(
+        throwUnsupportedOperationException(
             "transformFrom is not implemented on this LayoutCoordinates"
         )
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
index 5752aa8b..c2df497 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
@@ -29,11 +29,11 @@
         measurables: List<Measurable>,
         constraints: Constraints
     ): MeasureResult {
-        return when {
-            measurables.isEmpty() -> {
+        return when (measurables.size) {
+            0 -> {
                 layout(constraints.minWidth, constraints.minHeight) {}
             }
-            measurables.size == 1 -> {
+            1 -> {
                 val placeable = measurables[0].measure(constraints)
                 layout(
                     constraints.constrainWidth(placeable.width),
@@ -43,13 +43,15 @@
                 }
             }
             else -> {
-                val placeables = measurables.fastMap { it.measure(constraints) }
                 var maxWidth = 0
                 var maxHeight = 0
-                placeables.fastForEach { placeable ->
-                    maxWidth = maxOf(placeable.width, maxWidth)
-                    maxHeight = maxOf(placeable.height, maxHeight)
-                }
+                val placeables =
+                    measurables.fastMap {
+                        it.measure(constraints).apply {
+                            maxWidth = maxOf(width, maxWidth)
+                            maxHeight = maxOf(height, maxHeight)
+                        }
+                    }
                 layout(
                     constraints.constrainWidth(maxWidth),
                     constraints.constrainHeight(maxHeight)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt
index 60704c6..d02222d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt
@@ -14,52 +14,40 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
 package androidx.compose.ui.layout
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.isSpecified
-import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.util.packFloats
 import androidx.compose.ui.util.unpackFloat1
 import androidx.compose.ui.util.unpackFloat2
 
 /** Constructs a [ScaleFactor] from the given x and y scale values */
-@Stable fun ScaleFactor(scaleX: Float, scaleY: Float) = ScaleFactor(packFloats(scaleX, scaleY))
+@Stable
+inline fun ScaleFactor(scaleX: Float, scaleY: Float) = ScaleFactor(packFloats(scaleX, scaleY))
 
 /** Holds 2 dimensional scaling factors for horizontal and vertical axes */
 @Immutable
 @kotlin.jvm.JvmInline
-value class ScaleFactor internal constructor(@PublishedApi internal val packedValue: Long) {
+value class ScaleFactor(val packedValue: Long) {
 
     /** Returns the scale factor to apply along the horizontal axis */
     @Stable
-    val scaleX: Float
-        get() {
-            // Explicitly compare against packed values to avoid
-            // auto-boxing of ScaleFactor.Unspecified
-            checkPrecondition(this.packedValue != Unspecified.packedValue) {
-                "ScaleFactor is unspecified"
-            }
-            return unpackFloat1(packedValue)
-        }
+    inline val scaleX: Float
+        get() = unpackFloat1(packedValue)
 
     /** Returns the scale factor to apply along the vertical axis */
     @Stable
-    val scaleY: Float
-        get() {
-            // Explicitly compare against packed values to avoid
-            // auto-boxing of Size.Unspecified
-            checkPrecondition(this.packedValue != Unspecified.packedValue) {
-                "ScaleFactor is unspecified"
-            }
-            return unpackFloat2(packedValue)
-        }
+    inline val scaleY: Float
+        get() = unpackFloat2(packedValue)
 
-    @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component1(): Float = scaleX
+    @Stable inline operator fun component1(): Float = scaleX
 
-    @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component2(): Float = scaleY
+    @Stable inline operator fun component2(): Float = scaleY
 
     /**
      * Returns a copy of this ScaleFactor instance optionally overriding the scaleX or scaleY
@@ -84,7 +72,6 @@
     override fun toString() = "ScaleFactor(${scaleX}, ${scaleY})"
 
     companion object {
-
         /**
          * A ScaleFactor whose [scaleX] and [scaleY] parameters are unspecified. This is a sentinel
          * value used to initialize a non-null parameter. Access to scaleX or scaleY on an
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 08a5f0c..2edc023 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
@@ -18,6 +18,7 @@
 
 import androidx.collection.MutableOrderedScatterSet
 import androidx.collection.mutableOrderedScatterSetOf
+import androidx.collection.mutableScatterMapOf
 import androidx.compose.runtime.Applier
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ComposeNodeLifecycleCallback
@@ -36,6 +37,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.internal.checkPrecondition
+import androidx.compose.ui.internal.requirePrecondition
+import androidx.compose.ui.internal.throwIllegalStateExceptionForNullCheck
+import androidx.compose.ui.internal.throwIndexOutOfBoundsException
 import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
 import androidx.compose.ui.materialize
 import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
@@ -405,17 +409,19 @@
 
     private var currentIndex = 0
     private var currentPostLookaheadIndex = 0
-    private val nodeToNodeState = hashMapOf<LayoutNode, NodeState>()
+    private val nodeToNodeState = mutableScatterMapOf<LayoutNode, NodeState>()
 
     // this map contains active slotIds (without precomposed or reusable nodes)
-    private val slotIdToNode = hashMapOf<Any?, LayoutNode>()
+    private val slotIdToNode = mutableScatterMapOf<Any?, LayoutNode>()
     private val scope = Scope()
     private val postLookaheadMeasureScope = PostLookaheadMeasureScopeImpl()
 
-    private val precomposeMap = hashMapOf<Any?, LayoutNode>()
+    private val precomposeMap = mutableScatterMapOf<Any?, LayoutNode>()
     private val reusableSlotIdsSet = SubcomposeSlotReusePolicy.SlotIdsSet()
+
     // SlotHandles precomposed in the post-lookahead pass.
-    private val postLookaheadPrecomposeSlotHandleMap = mutableMapOf<Any?, PrecomposedSlotHandle>()
+    private val postLookaheadPrecomposeSlotHandleMap =
+        mutableScatterMapOf<Any?, PrecomposedSlotHandle>()
     // Slot ids _composed_ in post-lookahead. The valid slot ids are stored between 0 and
     // currentPostLookaheadIndex - 1, beyond index currentPostLookaheadIndex are obsolete ids.
     private val postLookaheadComposedSlotIds = mutableVectorOf<Any?>()
@@ -471,7 +477,7 @@
         if (root.foldedChildren.getOrNull(currentIndex) !== node) {
             // the node has a new index in the list
             val itemIndex = root.foldedChildren.indexOf(node)
-            require(itemIndex >= currentIndex) {
+            requirePrecondition(itemIndex >= currentIndex) {
                 "Key \"$slotId\" was already used. If you are using LazyColumn/Row please make " +
                     "sure you provide a unique key for each item."
             }
@@ -509,7 +515,10 @@
                         existing = nodeState.composition,
                         container = node,
                         parent =
-                            compositionContext ?: error("parent composition reference not set"),
+                            compositionContext
+                                ?: throwIllegalStateExceptionForNullCheck(
+                                    "parent composition reference not set"
+                                ),
                         reuseContent = nodeState.forceReuse,
                         composable = { ReusableContentHost(nodeState.active, content) }
                     )
@@ -539,20 +548,21 @@
             }
     }
 
-    private fun getSlotIdAtIndex(index: Int): Any? {
-        val node = root.foldedChildren[index]
+    private fun getSlotIdAtIndex(foldedChildren: List<LayoutNode>, index: Int): Any? {
+        val node = foldedChildren[index]
         return nodeToNodeState[node]!!.slotId
     }
 
     fun disposeOrReuseStartingFromIndex(startIndex: Int) {
         reusableCount = 0
-        val lastReusableIndex = root.foldedChildren.size - precomposedCount - 1
+        val foldedChildren = root.foldedChildren
+        val lastReusableIndex = foldedChildren.size - precomposedCount - 1
         var needApplyNotification = false
         if (startIndex <= lastReusableIndex) {
             // construct the set of available slot ids
             reusableSlotIdsSet.clear()
             for (i in startIndex..lastReusableIndex) {
-                val slotId = getSlotIdAtIndex(i)
+                val slotId = getSlotIdAtIndex(foldedChildren, i)
                 reusableSlotIdsSet.add(slotId)
             }
 
@@ -561,7 +571,7 @@
             var i = lastReusableIndex
             Snapshot.withoutReadObservation {
                 while (i >= startIndex) {
-                    val node = root.foldedChildren[i]
+                    val node = foldedChildren[i]
                     val nodeState = nodeToNodeState[node]!!
                     val slotId = nodeState.slotId
                     if (slotId in reusableSlotIdsSet) {
@@ -596,12 +606,13 @@
         precomposedCount = 0
         precomposeMap.clear()
 
-        val childCount = root.foldedChildren.size
+        val foldedChildren = root.foldedChildren
+        val childCount = foldedChildren.size
         if (reusableCount != childCount) {
             reusableCount = childCount
             Snapshot.withoutReadObservation {
                 for (i in 0 until childCount) {
-                    val node = root.foldedChildren[i]
+                    val node = foldedChildren[i]
                     val nodeState = nodeToNodeState[node]
                     if (nodeState != null && nodeState.active) {
                         node.resetLayoutState()
@@ -624,7 +635,7 @@
 
     private fun disposeCurrentNodes() {
         root.ignoreRemeasureRequests {
-            nodeToNodeState.values.forEach { it.composition?.dispose() }
+            nodeToNodeState.forEachValue { it.composition?.dispose() }
             root.removeAll()
         }
 
@@ -639,17 +650,17 @@
 
     fun makeSureStateIsConsistent() {
         val childrenCount = root.foldedChildren.size
-        require(nodeToNodeState.size == childrenCount) {
+        requirePrecondition(nodeToNodeState.size == childrenCount) {
             "Inconsistency between the count of nodes tracked by the state " +
                 "(${nodeToNodeState.size}) and the children count on the SubcomposeLayout" +
                 " ($childrenCount). Are you trying to use the state of the" +
                 " disposed SubcomposeLayout?"
         }
-        require(childrenCount - reusableCount - precomposedCount >= 0) {
+        requirePrecondition(childrenCount - reusableCount - precomposedCount >= 0) {
             "Incorrect state. Total children $childrenCount. Reusable children " +
                 "$reusableCount. Precomposed children $precomposedCount"
         }
-        require(precomposeMap.size == precomposedCount) {
+        requirePrecondition(precomposeMap.size == precomposedCount) {
             "Incorrect state. Precomposed children $precomposedCount. Map size " +
                 "${precomposeMap.size}"
         }
@@ -664,13 +675,14 @@
         if (reusableCount == 0) {
             return null
         }
-        val reusableNodesSectionEnd = root.foldedChildren.size - precomposedCount
+        val foldedChildren = root.foldedChildren
+        val reusableNodesSectionEnd = foldedChildren.size - precomposedCount
         val reusableNodesSectionStart = reusableNodesSectionEnd - reusableCount
         var index = reusableNodesSectionEnd - 1
         var chosenIndex = -1
         // first try to find a node with exactly the same slotId
         while (index >= reusableNodesSectionStart) {
-            if (getSlotIdAtIndex(index) == slotId) {
+            if (getSlotIdAtIndex(foldedChildren, index) == slotId) {
                 // we have a node with the same slotId
                 chosenIndex = index
                 break
@@ -682,7 +694,7 @@
             // try to find a first compatible slotId from the end of the section
             index = reusableNodesSectionEnd - 1
             while (index >= reusableNodesSectionStart) {
-                val node = root.foldedChildren[index]
+                val node = foldedChildren[index]
                 val nodeState = nodeToNodeState[node]!!
                 if (
                     nodeState.slotId === ReusedSlotId ||
@@ -704,7 +716,7 @@
                 move(index, reusableNodesSectionStart, 1)
             }
             reusableCount--
-            val node = root.foldedChildren[reusableNodesSectionStart]
+            val node = foldedChildren[reusableNodesSectionStart]
             val nodeState = nodeToNodeState[node]!!
             // create a new instance to avoid change notifications
             nodeState.activeState = mutableStateOf(true)
@@ -726,6 +738,7 @@
                 scope.density = density
                 scope.fontScale = fontScale
                 if (!isLookingAhead && root.lookaheadRoot != null) {
+                    // Approach pass
                     currentPostLookaheadIndex = 0
                     val result = postLookaheadMeasureScope.block(constraints)
                     val indexAfterMeasure = currentPostLookaheadIndex
@@ -736,6 +749,7 @@
                         disposeUnusedSlotsInPostLookahead()
                     }
                 } else {
+                    // Lookahead pass, or the main pass if not in a lookahead scope.
                     currentIndex = 0
                     val result = scope.block(constraints)
                     val indexAfterMeasure = currentIndex
@@ -750,7 +764,7 @@
     }
 
     private fun disposeUnusedSlotsInPostLookahead() {
-        postLookaheadPrecomposeSlotHandleMap.entries.removeAll { (slotId, handle) ->
+        postLookaheadPrecomposeSlotHandleMap.removeIf { slotId, handle ->
             val id = postLookaheadComposedSlotIds.indexOf(slotId)
             if (id < 0 || id >= currentPostLookaheadIndex) {
                 // Slot was not used in the latest pass of post-lookahead.
@@ -813,9 +827,9 @@
                 makeSureStateIsConsistent()
                 val node = precomposeMap.remove(slotId)
                 if (node != null) {
-                    check(precomposedCount > 0) { "No pre-composed items to dispose" }
+                    checkPrecondition(precomposedCount > 0) { "No pre-composed items to dispose" }
                     val itemIndex = root.foldedChildren.indexOf(node)
-                    check(itemIndex >= root.foldedChildren.size - precomposedCount) {
+                    checkPrecondition(itemIndex >= root.foldedChildren.size - precomposedCount) {
                         "Item is not in pre-composed item range"
                     }
                     // move this item into the reusable section
@@ -835,11 +849,13 @@
                 if (node != null && node.isAttached) {
                     val size = node.children.size
                     if (index < 0 || index >= size) {
-                        throw IndexOutOfBoundsException(
+                        throwIndexOutOfBoundsException(
                             "Index ($index) is out of bound of [0, $size)"
                         )
                     }
-                    require(!node.isPlaced) { "Pre-measure called on node that is not placed" }
+                    requirePrecondition(!node.isPlaced) {
+                        "Pre-measure called on node that is not placed"
+                    }
                     root.ignoreRemeasureRequests {
                         node.requireOwner().measureAndLayout(node.children[index], constraints)
                     }
@@ -860,7 +876,7 @@
         if (reusableCount != childCount) {
             // only invalidate children if there are any non-reused ones
             // in other cases, all of them are going to be invalidated later anyways
-            nodeToNodeState.forEach { (_, nodeState) -> nodeState.forceRecompose = true }
+            nodeToNodeState.forEachValue { nodeState -> nodeState.forceRecompose = true }
 
             if (!root.measurePending) {
                 root.requestRemeasure()
@@ -951,11 +967,14 @@
          * pass, [subcompose] will return an [emptyList].
          */
         override fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> {
-            val measurables = slotIdToNode[slotId]?.childMeasurables
-            if (measurables != null) {
-                return measurables
+            val nodeInSlot = slotIdToNode[slotId]
+            if (nodeInSlot != null && root.foldedChildren.indexOf(nodeInSlot) < currentIndex) {
+                // Check that the node has been composed in lookahead. Otherwise, we need to
+                // compose the node in approach pass via postLookaheadSubcompose.
+                return nodeInSlot.childMeasurables
+            } else {
+                return postLookaheadSubcompose(slotId, content)
             }
-            return postLookaheadSubcompose(slotId, content)
         }
     }
 
@@ -963,7 +982,7 @@
         slotId: Any?,
         content: @Composable () -> Unit
     ): List<Measurable> {
-        require(postLookaheadComposedSlotIds.size >= currentPostLookaheadIndex) {
+        requirePrecondition(postLookaheadComposedSlotIds.size >= currentPostLookaheadIndex) {
             "Error: currentPostLookaheadIndex cannot be greater than the size of the" +
                 "postLookaheadComposedSlotIds list."
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index 0e8e58f..e32ceea 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
 
@@ -81,7 +82,9 @@
                     parent.measurePending ||
                     parent.layoutPending ||
                     parentLayoutState == LayoutNode.LayoutState.Measuring ||
-                    parentLayoutState == LayoutNode.LayoutState.LayingOut
+                    parentLayoutState == LayoutNode.LayoutState.LayingOut ||
+                    postponedMeasureRequests.fastAny { it.node == this } ||
+                    layoutState == LayoutNode.LayoutState.Measuring
             }
         }
         if (isPlacedInLookahead == true) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
index 8bf8d59..708fec5 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
@@ -19,8 +19,10 @@
 import androidx.collection.MutableObjectFloatMap
 import androidx.collection.MutableScatterMap
 import androidx.collection.MutableScatterSet
+import androidx.collection.mutableObjectIntMapOf
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.internal.checkPrecondition
+import androidx.compose.ui.internal.throwIllegalStateExceptionForNullCheck
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadLayoutCoordinates
@@ -358,7 +360,7 @@
     override val measureResult: MeasureResult
         get() =
             _measureResult
-                ?: error(
+                ?: throwIllegalStateExceptionForNullCheck(
                     "LookaheadDelegate has not been measured yet when measureResult is requested."
                 )
 
@@ -417,10 +419,10 @@
             field = result
         }
 
-    protected val cachedAlignmentLinesMap = mutableMapOf<AlignmentLine, Int>()
+    protected val cachedAlignmentLinesMap = mutableObjectIntMapOf<AlignmentLine>()
 
     internal fun getCachedAlignmentLine(alignmentLine: AlignmentLine): Int =
-        cachedAlignmentLinesMap[alignmentLine] ?: AlignmentLine.Unspecified
+        cachedAlignmentLinesMap.getOrDefault(alignmentLine, AlignmentLine.Unspecified)
 
     override fun replace() {
         placeAt(position, 0f, null)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MyersDiff.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MyersDiff.kt
index fc0a078..acc7620 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MyersDiff.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MyersDiff.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:Suppress("NOTHING_TO_INLINE")
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
 
 package androidx.compose.ui.node
 
@@ -180,7 +180,7 @@
 ): Boolean {
     val oldSize = oldEnd - oldStart
     val newSize = newEnd - newStart
-    val checkForSnake = abs(oldSize - newSize) % 2 == 1
+    val checkForSnake = (abs(oldSize - newSize) and 1) == 1
     val delta = oldSize - newSize
     var k = -d
     while (k <= d) {
@@ -200,7 +200,7 @@
             x = startX + 1
         }
         var y: Int = newStart + (x - oldStart) - k
-        startY = if (d == 0 || x != startX) y else y - 1
+        startY = y - ((d != 0) and (x == startX)).toInt()
         // now find snake size
         while ((x < oldEnd) && y < newEnd && cb.areItemsTheSame(x, y)) {
             x++
@@ -244,7 +244,7 @@
 ): Boolean {
     val oldSize = oldEnd - oldStart
     val newSize = newEnd - newStart
-    val checkForSnake = (oldSize - newSize) % 2 == 0
+    val checkForSnake = ((oldSize - newSize) and 1) == 0
     val delta = oldSize - newSize
     // same as androidx.compose.ui.node.forward but we go backwards from end of the lists to be
     // beginning this also means we'll try to optimize for minimizing x instead of maximizing it
@@ -267,7 +267,7 @@
             x = startX - 1
         }
         var y = newEnd - (oldEnd - x - k)
-        val startY = if (d == 0 || x != startX) y else y + 1
+        val startY = y + ((d != 0) and (x == startX)).toInt()
         // now find snake size
         while ((x > oldStart) && y > newStart && cb.areItemsTheSame(x - 1, y - 1)) {
             x--
@@ -306,26 +306,26 @@
 @JvmInline
 private value class Snake(val data: IntArray) {
     /** Position in the old list */
-    val startX: Int
+    inline val startX: Int
         get() = data[0]
 
     /** Position in the new list */
-    val startY: Int
+    inline val startY: Int
         get() = data[1]
 
     /** End position in the old list, exclusive */
-    val endX: Int
+    inline val endX: Int
         get() = data[2]
 
     /** End position in the new list, exclusive */
-    val endY: Int
+    inline val endY: Int
         get() = data[3]
 
     /** True if this snake was created in the reverse search, false otherwise. */
-    val reverse: Boolean
+    inline val reverse: Boolean
         get() = data[4] != 0
 
-    val diagonalSize: Int
+    inline val diagonalSize: Int
         get() = min(endX - startX, endY - startY)
 
     private val hasAdditionOrRemoval: Boolean
@@ -339,27 +339,41 @@
      * where we try to produce a path and also find moves.
      */
     fun addDiagonalToStack(diagonals: IntStack) {
+        // if (hasAdditionOrRemoval) {
+        //     if (reverse) {
+        //         x = startX
+        //         y = startY
+        //     } else {
+        //         if (isAddition) {
+        //             x = startX
+        //             y = startY + 1
+        //         } else {
+        //             x = startX + 1
+        //             y = startY
+        //         }
+        //     }
+        // } else {
+        //     x = startX
+        //     y = startY
+        // }
+        val size: Int
+        var x = startX
+        var y = startY
         if (hasAdditionOrRemoval) {
-            if (reverse) {
-                // snake edge it at the end
-                diagonals.pushDiagonal(startX, startY, diagonalSize)
-            } else {
-                // snake edge it at the beginning
-                if (isAddition) {
-                    diagonals.pushDiagonal(startX, startY + 1, diagonalSize)
-                } else {
-                    diagonals.pushDiagonal(startX + 1, startY, diagonalSize)
-                }
-            }
+            size = diagonalSize
+            x += (!(reverse or isAddition)).toInt()
+            y += (!(reverse or !isAddition)).toInt()
         } else {
-            // we are a pure diagonal
-            diagonals.pushDiagonal(startX, startY, endX - startX)
+            size = endX - startX
         }
+        diagonals.pushDiagonal(x, y, size)
     }
 
     override fun toString() = "Snake($startX,$startY,$endX,$endY,$reverse)"
 }
 
+private inline fun Boolean.toInt() = if (this) 1 else 0
+
 internal fun fillSnake(
     startX: Int,
     startY: Int,
@@ -403,6 +417,12 @@
     val size: Int
         get() = lastIndex
 
+    private fun resizeStack(stack: IntArray): IntArray {
+        val copy = stack.copyOf(stack.size * 2)
+        this.stack = copy
+        return copy
+    }
+
     fun pushRange(
         oldStart: Int,
         oldEnd: Int,
@@ -410,10 +430,10 @@
         newEnd: Int,
     ) {
         val i = lastIndex
+        var stack = stack
         if (i + 4 >= stack.size) {
-            stack = stack.copyOf(stack.size * 2)
+            stack = resizeStack(stack)
         }
-        val stack = stack
         stack[i + 0] = oldStart
         stack[i + 1] = oldEnd
         stack[i + 2] = newStart
@@ -427,10 +447,10 @@
         size: Int,
     ) {
         val i = lastIndex
+        var stack = stack
         if (i + 3 >= stack.size) {
-            stack = stack.copyOf(stack.size * 2)
+            stack = resizeStack(stack)
         }
-        val stack = stack
         stack[i + 0] = x + size
         stack[i + 1] = y + size
         stack[i + 2] = size
diff --git a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUConfig.java b/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUConfig.java
deleted file mode 100644
index e43da90..0000000
--- a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUConfig.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- *******************************************************************************
- * Copyright (C) 2008-2010, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
- *******************************************************************************
- */
-package androidx.core.i18n.messageformat_icu.impl;
-
-import androidx.annotation.RestrictTo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.AccessControlException;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.MissingResourceException;
-import java.util.Properties;
-
-/**
- * ICUConfig is a class used for accessing ICU4J runtime configuration.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class ICUConfig {
-    public static final String CONFIG_PROPS_FILE = "/com/ibm/icu/ICUConfig.properties";
-    private static final Properties CONFIG_PROPS;
-
-    static {
-        CONFIG_PROPS = new Properties();
-        try {
-            InputStream is = ICUData.getStream(CONFIG_PROPS_FILE);
-            if (is != null) {
-                CONFIG_PROPS.load(is);
-            }
-        } catch (MissingResourceException mre) {
-            // If it does not exist, ignore.
-        } catch (IOException ioe) {
-            // Any IO errors, ignore
-        }
-    }
-
-    /**
-     * Get ICU configuration property value for the given name.
-     * @param name The configuration property name
-     * @return The configuration property value, or null if it does not exist.
-     */
-    public static String get(String name) {
-        return get(name, null);
-    }
-
-    /**
-     * Get ICU configuration property value for the given name.
-     * @param name The configuration property name
-     * @param def The default value
-     * @return The configuration property value.  If the property does not
-     * exist, <code>def</code> is returned.
-     */
-    public static String get(String name, String def) {
-        String val = null;
-        final String fname = name;
-        if (System.getSecurityManager() != null) {
-            try {
-                val = AccessController.doPrivileged(new PrivilegedAction<String>() {
-                    @Override
-                    public String run() {
-                        return System.getProperty(fname);
-                    }
-                });
-            } catch (AccessControlException e) {
-                // ignore
-                // TODO log this message
-            }
-        } else {
-            val = System.getProperty(name);
-        }
-
-        if (val == null) {
-            val = CONFIG_PROPS.getProperty(name, def);
-        }
-        return val;
-    }
-}
diff --git a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUData.java b/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUData.java
deleted file mode 100644
index a20acca..0000000
--- a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/impl/ICUData.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- *******************************************************************************
- * Copyright (C) 2004-2009, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
- *******************************************************************************
- *
- * Created on Feb 4, 2004
- *
- */
-package androidx.core.i18n.messageformat_icu.impl;
-
-import androidx.annotation.RestrictTo;
-
-import java.io.InputStream;
-import java.net.URL;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.MissingResourceException;
-
-/**
- * Provides access to ICU data files as InputStreams.  Implements security checking.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public final class ICUData {
-    /*
-     * Return a URL to the ICU resource names resourceName.  The
-     * resource name should either be an absolute path, or a path relative to
-     * com.ibm.icu.impl (e.g., most likely it is 'data/foo').  If required
-     * is true, throw an MissingResourceException instead of returning a null result.
-     */
-    public static boolean exists(final String resourceName) {
-        URL i = null;
-        if (System.getSecurityManager() != null) {
-            i = AccessController.doPrivileged(new PrivilegedAction<URL>() {
-                    @Override
-                    public URL run() {
-                        return ICUData.class.getResource(resourceName);
-                    }
-                });
-        } else {
-            i = ICUData.class.getResource(resourceName);
-        }
-        return i != null;
-    }
-        
-    private static InputStream getStream(final Class<?> root, final String resourceName, boolean required) {
-        InputStream i = null;
-        
-        if (System.getSecurityManager() != null) {
-            i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
-                    @Override
-                    public InputStream run() {
-                        return root.getResourceAsStream(resourceName);
-                    }
-                });
-        } else {
-            i = root.getResourceAsStream(resourceName);
-        }
-
-        if (i == null && required) {
-            throw new MissingResourceException("could not locate data " +resourceName, root.getPackage().getName(), resourceName);
-        }
-        return i;
-    }
-
-    private static InputStream getStream(final ClassLoader loader, final String resourceName, boolean required) {
-        InputStream i = null;
-        if (System.getSecurityManager() != null) {
-            i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
-                    @Override
-                    public InputStream run() {
-                        return loader.getResourceAsStream(resourceName);
-                    }
-                });
-        } else {
-            i = loader.getResourceAsStream(resourceName);
-        }
-        if (i == null && required) {
-            throw new MissingResourceException("could not locate data", loader.toString(), resourceName);
-        }
-        return i;
-    }
-    
-    public static InputStream getStream(ClassLoader loader, String resourceName){
-        return getStream(loader,resourceName, false);   
-    }
-
-    public static InputStream getRequiredStream(ClassLoader loader, String resourceName){
-        return getStream(loader, resourceName, true);
-    }
-
-    /*
-     * Convenience override that calls getStream(ICUData.class, resourceName, false);
-     */
-    public static InputStream getStream(String resourceName) {
-        return getStream(ICUData.class, resourceName, false);
-    }
-        
-    /*
-     * Convenience method that calls getStream(ICUData.class, resourceName, true).
-     */
-    public static InputStream getRequiredStream(String resourceName) {
-        return getStream(ICUData.class, resourceName, true);
-    }
-
-    /*
-     * Convenience override that calls getStream(root, resourceName, false);
-     */
-    public static InputStream getStream(Class<?> root, String resourceName) {
-        return getStream(root, resourceName, false);
-    }
-    
-    /*
-     * Convenience method that calls getStream(root, resourceName, true).
-     */
-    public static InputStream getRequiredStream(Class<?> root, String resourceName) {
-        return getStream(root, resourceName, true);
-    }
-}
diff --git a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/text/MessagePattern.java b/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/text/MessagePattern.java
index b0b190d7..d494d46 100644
--- a/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/text/MessagePattern.java
+++ b/core/core-i18n/src/main/java/androidx/core/i18n/messageformat_icu/text/MessagePattern.java
@@ -17,8 +17,6 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-import androidx.core.i18n.messageformat_icu.impl.ICUConfig;
-
 //Note: Minimize ICU dependencies, only use a very small part of the ICU core.
 //In particular, do not depend on *Format classes.
 
@@ -82,8 +80,7 @@
 public final class MessagePattern implements Cloneable, Freezable<MessagePattern> {
     /**
      * Mode for when an apostrophe starts quoted literal text for MessageFormat output.
-     * The default is DOUBLE_OPTIONAL unless overridden via ICUConfig
-     * (/com/ibm/icu/ICUConfig.properties).
+     * The default is {@link ApostropheMode#DOUBLE_OPTIONAL}.
      * <p>
      * A pair of adjacent apostrophes always results in a single apostrophe in the output,
      * even when the pair is between two single, text-quoting apostrophes.
@@ -893,7 +890,7 @@
 
     /**
      * Freezes this object, making it immutable and thread-safe.
-     * @return this 
+     * @return this
      * icu_annot::stable ICU 4.8
      */
     @Override
@@ -941,7 +938,7 @@
             char c=msg.charAt(index++);
             if(c=='\'') {
                 if(index==msg.length()) {
-                    // The apostrophe is the last character in the pattern. 
+                    // The apostrophe is the last character in the pattern.
                     // Add a Part for auto-quoting.
                     addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
                     needsAutoQuoting=true;
@@ -1612,8 +1609,7 @@
     private boolean frozen;
 
     private static final ApostropheMode defaultAposMode=
-        ApostropheMode.valueOf(
-            ICUConfig.get("com.ibm.icu.text.MessagePattern.ApostropheMode", "DOUBLE_OPTIONAL"));
+        ApostropheMode.DOUBLE_OPTIONAL;
 
     private static final ArgType[] argTypes=ArgType.values();
 }
diff --git a/core/core-telecom/api/current.txt b/core/core-telecom/api/current.txt
index 40a8a93..8468a41 100644
--- a/core/core-telecom/api/current.txt
+++ b/core/core-telecom/api/current.txt
@@ -137,7 +137,7 @@
   }
 
   @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ExtensionInitializationScope {
-    method public androidx.core.telecom.extensions.LocalCallSilenceExtension addLocalSilenceExtension(boolean initialCallSilenceState, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onLocalSilenceUpdate);
+    method public androidx.core.telecom.extensions.LocalCallSilenceExtension addLocalCallSilenceExtension(boolean initialCallSilenceState, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onLocalSilenceUpdate);
     method public androidx.core.telecom.extensions.ParticipantExtension addParticipantExtension(optional java.util.Set<androidx.core.telecom.extensions.Participant> initialParticipants, optional androidx.core.telecom.extensions.Participant? initialActiveParticipant);
     method public void onCall(kotlin.jvm.functions.Function2<? super androidx.core.telecom.CallControlScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onCall);
   }
diff --git a/core/core-telecom/api/restricted_current.txt b/core/core-telecom/api/restricted_current.txt
index 40a8a93..8468a41 100644
--- a/core/core-telecom/api/restricted_current.txt
+++ b/core/core-telecom/api/restricted_current.txt
@@ -137,7 +137,7 @@
   }
 
   @SuppressCompatibility @androidx.core.telecom.util.ExperimentalAppActions public interface ExtensionInitializationScope {
-    method public androidx.core.telecom.extensions.LocalCallSilenceExtension addLocalSilenceExtension(boolean initialCallSilenceState, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onLocalSilenceUpdate);
+    method public androidx.core.telecom.extensions.LocalCallSilenceExtension addLocalCallSilenceExtension(boolean initialCallSilenceState, kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onLocalSilenceUpdate);
     method public androidx.core.telecom.extensions.ParticipantExtension addParticipantExtension(optional java.util.Set<androidx.core.telecom.extensions.Participant> initialParticipants, optional androidx.core.telecom.extensions.Participant? initialActiveParticipant);
     method public void onCall(kotlin.jvm.functions.Function2<? super androidx.core.telecom.CallControlScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> onCall);
   }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
index e97f365..a3353b7 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/VoipAppWithExtensions/VoipCall.kt
@@ -84,7 +84,7 @@
                 }
                 Extensions.LOCAL_CALL_SILENCE -> {
                     localCallSilenceUpdater =
-                        addLocalSilenceExtension(false) {
+                        addLocalCallSilenceExtension(false) {
                             Log.i(TAG, "addLocalSilenceExtension: callId=[$callId], it=[$it]")
                             callback?.setLocalCallSilenceState(callId, it)
                         }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
index e21ad2d..4ff6f6a 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScope.kt
@@ -117,7 +117,7 @@
      * @return The interface used by this application to further update the local call silence
      *   extension state to remote surfaces
      */
-    public fun addLocalSilenceExtension(
+    public fun addLocalCallSilenceExtension(
         initialCallSilenceState: Boolean,
         onLocalSilenceUpdate: (suspend (Boolean) -> Unit),
     ): LocalCallSilenceExtension
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt
index 20b900f..7d923d4 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/ExtensionInitializationScopeImpl.kt
@@ -64,7 +64,7 @@
         return participant
     }
 
-    override fun addLocalSilenceExtension(
+    override fun addLocalCallSilenceExtension(
         initialCallSilenceState: Boolean,
         onLocalSilenceUpdate: (suspend (Boolean) -> Unit)
     ): LocalCallSilenceExtension {
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/LocalCallSilenceExtension.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/LocalCallSilenceExtension.kt
index 96e8b75..4b43ad3 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/LocalCallSilenceExtension.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/LocalCallSilenceExtension.kt
@@ -28,7 +28,7 @@
  * not transmitting audio input data to remote users. This allows applications to do stuff like
  * nudge the user when they are silenced but talking into the microphone.
  *
- * @see ExtensionInitializationScope.addLocalSilenceExtension
+ * @see ExtensionInitializationScope.addLocalCallSilenceExtension
  */
 @ExperimentalAppActions
 public interface LocalCallSilenceExtension {
diff --git a/credentials/credentials/build.gradle b/credentials/credentials/build.gradle
index ce89b90..2683d54 100644
--- a/credentials/credentials/build.gradle
+++ b/credentials/credentials/build.gradle
@@ -31,7 +31,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.8.1")
-    api("androidx.biometric:biometric-ktx:1.4.0-alpha02")
+    api("androidx.biometric:biometric:1.1.0")
     api(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesCore)
     implementation("androidx.core:core:1.15.0-alpha01")
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
index ef61508..b9a7f1b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
@@ -104,7 +104,7 @@
     }
 
     @Test
-    public void testGetCredentialAsyc_successCallbackThrows() throws InterruptedException {
+    public void testGetCredentialAsync_successCallbackThrows() throws InterruptedException {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
index 0a2d095..2c89de4 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
@@ -150,7 +150,8 @@
 
     @Test
     public void fromBundle_validAllowedAuthenticatorAboveApi35_success() {
-        long expectedOpId = TEST_CRYPTO_OBJECT.getOperationHandle();
+        long expectedOpId = BiometricTestUtils.INSTANCE
+                .getTestCryptoObjectOpId$credentials_releaseAndroidTest(TEST_CRYPTO_OBJECT);
         Bundle inputBundle = new Bundle();
         inputBundle.putInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS, TEST_ALLOWED_AUTHENTICATOR);
         inputBundle.putLong(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId);
@@ -160,9 +161,8 @@
         assertThat(actualBiometricPromptData).isNotNull();
         assertThat(actualBiometricPromptData.getAllowedAuthenticators()).isEqualTo(
                 TEST_ALLOWED_AUTHENTICATOR);
-        assertThat(actualBiometricPromptData.getCryptoObject()).isNotNull();
-        assertThat(actualBiometricPromptData.getCryptoObject().getOperationHandle())
-                .isEqualTo(TEST_CRYPTO_OBJECT.getOperationHandle());
+        assertThat(actualBiometricPromptData.getCryptoObject()).isNull();
+        // TODO(b/368395001) : Add CryptoObject test back when library dependency updates
     }
 
     @Test
@@ -211,7 +211,8 @@
     public void toBundle_api35AndAboveWithOpId_success() {
         BiometricPromptData testBiometricPromptData = new BiometricPromptData(TEST_CRYPTO_OBJECT,
                 TEST_ALLOWED_AUTHENTICATOR);
-        long expectedOpId = TEST_CRYPTO_OBJECT.getOperationHandle();
+        long expectedOpId = BiometricTestUtils.INSTANCE
+                .getTestCryptoObjectOpId$credentials_releaseAndroidTest(TEST_CRYPTO_OBJECT);
 
         Bundle actualBundle = BiometricPromptData.toBundle(
                 testBiometricPromptData);
@@ -231,5 +232,4 @@
                 () -> new BiometricPromptData.Builder().setAllowedAuthenticators(-10000).build()
         );
     }
-
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
index bdc1ea0..2f1bfc4 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricPrompt.CryptoObject
 import androidx.credentials.provider.BiometricPromptData.Companion.BUNDLE_HINT_ALLOWED_AUTHENTICATORS
 import androidx.credentials.provider.BiometricPromptData.Companion.BUNDLE_HINT_CRYPTO_OP_ID
 import androidx.credentials.provider.utils.BiometricTestUtils
@@ -143,7 +144,7 @@
 
     @Test
     fun fromBundle_validAllowedAuthenticatorAboveApi35_success() {
-        val expectedOpId = TEST_CRYPTO_OBJECT.operationHandle
+        val expectedOpId = getTestCryptoObjectOpId()
         val inputBundle = Bundle()
         inputBundle.putInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS, TEST_ALLOWED_AUTHENTICATOR)
         inputBundle.putLong(BUNDLE_HINT_CRYPTO_OP_ID, expectedOpId)
@@ -153,11 +154,13 @@
         assertThat(actualBiometricPromptData).isNotNull()
         assertThat(actualBiometricPromptData!!.allowedAuthenticators)
             .isEqualTo(TEST_ALLOWED_AUTHENTICATOR)
-        assertThat(actualBiometricPromptData.cryptoObject).isNotNull()
-        assertThat(actualBiometricPromptData.cryptoObject!!.operationHandle)
-            .isEqualTo(TEST_CRYPTO_OBJECT.operationHandle)
+        assertThat(actualBiometricPromptData.cryptoObject).isNull()
+        // TODO(b/368395001) : Add CryptoObject test back when library dependency updates
     }
 
+    private fun getTestCryptoObjectOpId(cryptoObject: CryptoObject = TEST_CRYPTO_OBJECT) =
+        BiometricTestUtils.getTestCryptoObjectOpId(cryptoObject)
+
     @Test
     fun fromBundle_unrecognizedAllowedAuthenticator_success() {
         val inputBundle = Bundle()
@@ -202,7 +205,7 @@
     fun toBundle_api35AndAboveWithOpId_success() {
         val testBiometricPromptData =
             BiometricPromptData(TEST_CRYPTO_OBJECT, TEST_ALLOWED_AUTHENTICATOR)
-        val expectedOpId = TEST_CRYPTO_OBJECT.operationHandle
+        val expectedOpId = getTestCryptoObjectOpId()
 
         val actualBundle = BiometricPromptData.toBundle(testBiometricPromptData)
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt
index b9ed941..0df3636 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/utils/BiometricTestUtils.kt
@@ -20,6 +20,7 @@
 import android.security.keystore.KeyProperties
 import androidx.annotation.RequiresApi
 import androidx.biometric.BiometricPrompt
+import androidx.biometric.BiometricPrompt.CryptoObject
 import java.security.KeyStore
 import javax.crypto.Cipher
 import javax.crypto.KeyGenerator
@@ -35,6 +36,13 @@
     private const val KEYSTORE_INSTANCE = "AndroidKeyStore"
 
     /**
+     * Retrieve the operationHandle for a CryptoObject by converting it to the framework equivalent
+     * type.
+     */
+    internal fun getTestCryptoObjectOpId(cryptoObject: CryptoObject) =
+        CryptoObjectUtils.getOperationHandle(cryptoObject = cryptoObject)
+
+    /**
      * Returns a [BiometricPrompt.CryptoObject] for crypto-based authentication. Adapted from:
      * [package androidx.biometric.samples.auth].
      */
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/BiometricPromptData.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/BiometricPromptData.kt
index c279cca..8ae0d32 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/BiometricPromptData.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/BiometricPromptData.kt
@@ -28,6 +28,7 @@
 import androidx.biometric.BiometricPrompt
 import androidx.biometric.BiometricPrompt.CryptoObject
 import androidx.core.os.BuildCompat
+import androidx.credentials.provider.utils.CryptoObjectUtils
 
 /**
  * Biometric prompt data that can be optionally used to provide information needed for the system to
@@ -44,11 +45,19 @@
  * determines whether a biometric auth or a device credential mechanism will / can be shown. The
  * value for this property is found in [Authenticators].
  *
+ * Finally, note that the CryptoObject does not presently support presentationSession and
+ * operationHandle constructors, and intends to support the CryptoObject built from the stable
+ * biometric jetpack library described
+ * [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0).
+ *
  * @property allowedAuthenticators specifies the type(s) of authenticators that may be invoked by
  *   the [BiometricPrompt] to authenticate the user, defaults to [BIOMETRIC_WEAK] if not set
  * @property cryptoObject a crypto object to be unlocked after successful authentication; When set,
  *   the value of [allowedAuthenticators] must be [BIOMETRIC_STRONG] or else an
- *   [IllegalArgumentException] is thrown
+ *   [IllegalArgumentException] is thrown, note that the CryptoObject does not presently support
+ *   presentationSession and operationHandle constructors, and intends to support the CryptoObject
+ *   built from the stable biometric jetpack library described
+ *   [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0)
  * @throws IllegalArgumentException if [cryptoObject] is not null, and the [allowedAuthenticators]
  *   is not set to [BIOMETRIC_STRONG]
  * @see Authenticators
@@ -76,11 +85,19 @@
      * determines whether a biometric auth or a device credential mechanism will / can be shown. The
      * value for this property is found in [Authenticators].
      *
+     * Finally, note that the [CryptoObject] does not presently support presentationSession and
+     * operationHandle constructors, and intends to support the CryptoObject built from the stable
+     * biometric jetpack library described
+     * [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0).
+     *
      * @param allowedAuthenticators specifies the type(s) of authenticators that may be invoked by
      *   the [BiometricPrompt] to authenticate the user, defaults to [BIOMETRIC_WEAK] if not set
      * @param cryptoObject a crypto object to be unlocked after successful authentication; When set,
      *   the value of [allowedAuthenticators] must be [BIOMETRIC_STRONG] or else an
-     *   [IllegalArgumentException] is thrown
+     *   [IllegalArgumentException] is thrown, note that the CryptoObject does not presently support
+     *   presentationSession and operationHandle constructors, and intends to support the
+     *   CryptoObject built from the stable biometric jetpack library described
+     *   [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0)
      * @throws IllegalArgumentException if [cryptoObject] is not null, and the
      *   [allowedAuthenticators] is not set to [BIOMETRIC_STRONG]
      * @see Authenticators
@@ -180,7 +197,10 @@
         /**
          * Sets whether this [BiometricPromptData] should have a crypto object associated with this
          * authentication. If opting to pass in a value, the [allowedAuthenticators] must be
-         * [BIOMETRIC_STRONG].
+         * [BIOMETRIC_STRONG]. The CryptoObject does not presently support presentationSession and
+         * operationHandle constructors and intends to support the CryptoObject built from the
+         * stable biometric jetpack library described
+         * [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0).
          *
          * @param cryptoObject the [CryptoObject] to be associated with this biometric
          *   authentication flow
@@ -224,6 +244,14 @@
     }
 
     private object ApiMinImpl {
+
+        /**
+         * Encapsulates the allowedAuthenticators from this [biometricPromptData] into a parcelable
+         * bundle.
+         *
+         * @param biometricPromptData the [BiometricPromptData] to convert into a bundle
+         * @return a bundle containing the representative bits of the [biometricPromptData]
+         */
         @JvmStatic
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         fun toBundle(biometricPromptData: BiometricPromptData): Bundle {
@@ -235,6 +263,12 @@
             return bundle
         }
 
+        /**
+         * Returns an instance of [BiometricPromptData] derived from a [Bundle] object.
+         *
+         * @param bundle the bundle with which to recover an instance of [BiometricPromptData] from
+         * @return an instance of [BiometricPromptData] recovered from the [bundle]
+         */
         @JvmStatic
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         fun fromBundle(bundle: Bundle): BiometricPromptData {
@@ -249,6 +283,13 @@
 
     private object Api35Impl {
 
+        /**
+         * Encapsulates the operationHandle and the allowed authenticator from this
+         * [biometricPromptData] into a parcelable bundle.
+         *
+         * @param biometricPromptData the [BiometricPromptData] to convert into a bundle
+         * @return a bundle containing the representative bits of the [biometricPromptData]
+         */
         @JvmStatic
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         fun toBundle(biometricPromptData: BiometricPromptData): Bundle {
@@ -258,24 +299,36 @@
                 biometricPromptData.allowedAuthenticators
             )
             biometricPromptData.cryptoObject?.let {
-                bundle.putLong(BUNDLE_HINT_CRYPTO_OP_ID, it.operationHandle)
+                bundle.putLong(
+                    BUNDLE_HINT_CRYPTO_OP_ID,
+                    CryptoObjectUtils.getOperationHandle(
+                        cryptoObject = biometricPromptData.cryptoObject
+                    )
+                )
             }
 
             return bundle
         }
 
+        /**
+         * Returns an instance of [BiometricPromptData] derived from a [Bundle] object.
+         *
+         * At present, the library owners do not support re-forming the [CryptoObject] from the
+         * bundle, as the library presently supports the stable AndroidX biometric library described
+         * [here](https://developer.android.com/jetpack/androidx/releases/biometric#1.1.0).
+         *
+         * @param bundle the bundle with which to recover an instance of [BiometricPromptData] from
+         * @return an instance of [BiometricPromptData] recovered from the [bundle]
+         */
         @JvmStatic
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         fun fromBundle(bundle: Bundle): BiometricPromptData {
-            var cryptoObject: CryptoObject? = null
-            if (bundle.containsKey(BUNDLE_HINT_CRYPTO_OP_ID)) {
-                val opId = bundle.getLong(BUNDLE_HINT_CRYPTO_OP_ID)
-                cryptoObject = CryptoObject(opId)
-            }
+            // TODO(b/368395001) : Once the AndroidX biometric library updates the stable
+            // release, re-form the cryptoObject.
             val biometricPromptData =
                 BiometricPromptData(
                     allowedAuthenticators = bundle.getInt(BUNDLE_HINT_ALLOWED_AUTHENTICATORS),
-                    cryptoObject = cryptoObject,
+                    cryptoObject = null,
                     isCreatedFromBundle = true,
                 )
             return biometricPromptData
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
index 83401bf..241a156 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
@@ -32,6 +32,7 @@
 import androidx.credentials.PasswordCredential
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.provider.CreateEntry.Api28Impl.addToSlice
+import androidx.credentials.provider.utils.CryptoObjectUtils.getOperationHandle
 import java.time.Instant
 import java.util.Collections
 
@@ -369,7 +370,7 @@
                 )
                 biometricPromptData.cryptoObject?.let {
                     sliceBuilder.addLong(
-                        biometricPromptData.cryptoObject.operationHandle,
+                        getOperationHandle(biometricPromptData.cryptoObject),
                         /*subType=*/ null,
                         listOf(SLICE_HINT_CRYPTO_OP_ID)
                     )
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
index 342bb81..a53b84b 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
@@ -32,6 +32,7 @@
 import androidx.credentials.CredentialOption
 import androidx.credentials.R
 import androidx.credentials.provider.CustomCredentialEntry.Api28Impl.addToSlice
+import androidx.credentials.provider.utils.CryptoObjectUtils.getOperationHandle
 import java.time.Instant
 import java.util.Collections
 
@@ -123,6 +124,11 @@
     }
 
     /**
+     * Custom credential entry for a custom credential type that is displayed on the account
+     * selector UI.
+     *
+     * Each entry corresponds to an account that can provide a credential.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param title the title shown with this entry on the selector UI
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -179,6 +185,11 @@
     )
 
     /**
+     * Custom credential entry for a custom credential type that is displayed on the account
+     * selector UI.
+     *
+     * Each entry corresponds to an account that can provide a credential.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param title the title shown with this entry on the selector UI
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -238,6 +249,11 @@
     )
 
     /**
+     * Custom credential entry for a custom credential type that is displayed on the account
+     * selector UI.
+     *
+     * Each entry corresponds to an account that can provide a credential.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param title the title shown with this entry on the selector UI
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -326,7 +342,7 @@
                 )
                 biometricPromptData.cryptoObject?.let {
                     sliceBuilder.addLong(
-                        biometricPromptData.cryptoObject.operationHandle,
+                        getOperationHandle(biometricPromptData.cryptoObject),
                         /*subType=*/ null,
                         listOf(SLICE_HINT_CRYPTO_OP_ID)
                     )
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
index 6f41b70..12e84c6 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
@@ -34,6 +34,7 @@
 import androidx.credentials.R
 import androidx.credentials.provider.PasswordCredentialEntry.Api28Impl.toSlice
 import androidx.credentials.provider.PasswordCredentialEntry.Companion.toSlice
+import androidx.credentials.provider.utils.CryptoObjectUtils.getOperationHandle
 import java.time.Instant
 import java.util.Collections
 
@@ -116,6 +117,15 @@
     }
 
     /**
+     * A password credential entry that is displayed on the account selector UI. This entry denotes
+     * that a credential of type [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] is available for the
+     * user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the password credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -174,6 +184,15 @@
     )
 
     /**
+     * A password credential entry that is displayed on the account selector UI. This entry denotes
+     * that a credential of type [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] is available for the
+     * user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the password credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -238,6 +257,15 @@
     )
 
     /**
+     * A password credential entry that is displayed on the account selector UI. This entry denotes
+     * that a credential of type [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] is available for the
+     * user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the password credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -327,7 +355,7 @@
                 )
                 biometricPromptData.cryptoObject?.let {
                     sliceBuilder.addLong(
-                        biometricPromptData.cryptoObject.operationHandle,
+                        getOperationHandle(biometricPromptData.cryptoObject),
                         /*subType=*/ null,
                         listOf(SLICE_HINT_CRYPTO_OP_ID)
                     )
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
index 9aefe535..b236cdd 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
@@ -34,6 +34,7 @@
 import androidx.credentials.R
 import androidx.credentials.provider.PublicKeyCredentialEntry.Api28Impl.toSlice
 import androidx.credentials.provider.PublicKeyCredentialEntry.Companion.toSlice
+import androidx.credentials.provider.utils.CryptoObjectUtils.getOperationHandle
 import java.time.Instant
 import java.util.Collections
 
@@ -122,6 +123,15 @@
     }
 
     /**
+     * A public key credential entry that is displayed on the account selector UI. This entry
+     * denotes that a credential of type [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL] is
+     * available for the user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the public key credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -170,6 +180,15 @@
     )
 
     /**
+     * A public key credential entry that is displayed on the account selector UI. This entry
+     * denotes that a credential of type [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL] is
+     * available for the user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the public key credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -224,6 +243,15 @@
     )
 
     /**
+     * A public key credential entry that is displayed on the account selector UI. This entry
+     * denotes that a credential of type [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL] is
+     * available for the user to select.
+     *
+     * Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
+     * can then show any activity they wish to. Before finishing the activity, provider must set the
+     * final [androidx.credentials.GetCredentialResponse] through the
+     * [PendingIntentHandler.setGetCredentialResponse] helper API.
+     *
      * @param context the context of the calling app, required to retrieve fallback resources
      * @param username the username of the account holding the public key credential
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
@@ -314,7 +342,7 @@
                 )
                 biometricPromptData.cryptoObject?.let {
                     sliceBuilder.addLong(
-                        biometricPromptData.cryptoObject.operationHandle,
+                        getOperationHandle(biometricPromptData.cryptoObject),
                         /*subType=*/ null,
                         listOf(SLICE_HINT_CRYPTO_OP_ID)
                     )
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/utils/CryptoObjectUtils.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/CryptoObjectUtils.kt
new file mode 100644
index 0000000..01bd74d
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/CryptoObjectUtils.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.provider.utils
+
+import android.hardware.biometrics.BiometricPrompt
+import android.os.Build
+import android.security.identity.IdentityCredential
+import androidx.annotation.RequiresApi
+import java.security.Signature
+import javax.crypto.Cipher
+import javax.crypto.Mac
+
+/**
+ * Utility class for creating and converting between different types of crypto objects that may be
+ * used internally by [androidx.biometric.BiometricPrompt] or the
+ * [androidx.biometric.BiometricManager].
+ *
+ * Borrowed from [package androidx.biometric] to support credential manager operations where both
+ * framework and jetpack data types are relevant.
+ *
+ * TODO(b/369394452) : Remove once biometrics new stable library reached.
+ */
+internal object CryptoObjectUtils {
+
+    /**
+     * Wraps a crypto object to be passed to [android.hardware.biometrics.BiometricPrompt].
+     *
+     * @param cryptoObject An instance of [androidx.biometric.BiometricPrompt.CryptoObject].
+     * @return An equivalent crypto object that is compatible with
+     *   [android.hardware.biometrics.BiometricPrompt].
+     */
+    @RequiresApi(Build.VERSION_CODES.P)
+    fun wrapForBiometricPrompt(
+        cryptoObject: androidx.biometric.BiometricPrompt.CryptoObject?
+    ): BiometricPrompt.CryptoObject? {
+        if (cryptoObject == null) {
+            return null
+        }
+
+        val cipher = cryptoObject.cipher
+        if (cipher != null) {
+            return Api28Impl.create(cipher)
+        }
+
+        val signature = cryptoObject.signature
+        if (signature != null) {
+            return Api28Impl.create(signature)
+        }
+
+        val mac = cryptoObject.mac
+        if (mac != null) {
+            return Api28Impl.create(mac)
+        }
+
+        // Identity credential is only supported on API 30 and above.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            val identityCredential = cryptoObject.identityCredential
+            if (identityCredential != null) {
+                return Api30Impl.create(identityCredential)
+            }
+        }
+
+        // Presentation session is only supported on API 33 and above, and thus not available in
+        // the supported stable library (biometrics 1.1.0).
+
+        // Operation handle is only supported on API 35 and above, and thus not available in
+        // the supported stable library (biometrics 1.1.0).
+        return null
+    }
+
+    /**
+     * Get the `operationHandle` associated with this object or 0 if none. This needs to be achieved
+     * by getting the corresponding [android.hardware.biometrics.BiometricPrompt.CryptoObject] and
+     * then get its operation handle.
+     *
+     * @param cryptoObject An instance of [androidx.biometric.BiometricPrompt.CryptoObject].
+     * @return The `operationHandle` associated with this object or 0 if none.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    fun getOperationHandle(cryptoObject: androidx.biometric.BiometricPrompt.CryptoObject?): Long {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            val wrappedCryptoObject = wrapForBiometricPrompt(cryptoObject)
+            if (wrappedCryptoObject != null) {
+                return Api35Impl.getOperationHandle(wrappedCryptoObject)
+            }
+        }
+        return 0
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 15.0 (API 35).
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private object Api35Impl {
+        /**
+         * Gets the operation handle associated with the given crypto object, if any.
+         *
+         * @param crypto An instance of [android.hardware.biometrics.BiometricPrompt.CryptoObject].
+         * @return The wrapped operation handle object, or `null`.
+         */
+        fun getOperationHandle(crypto: BiometricPrompt.CryptoObject): Long {
+            return crypto.operationHandle
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 11.0 (API 30).
+     */
+    @RequiresApi(Build.VERSION_CODES.R)
+    private object Api30Impl {
+        /**
+         * Creates an instance of the framework class
+         * [android.hardware.biometrics.BiometricPrompt.CryptoObject] from the given identity
+         * credential.
+         *
+         * @param identityCredential The identity credential object to be wrapped.
+         * @return An instance of [android.hardware.biometrics.BiometricPrompt.CryptoObject].
+         */
+        @Suppress("deprecation")
+        fun create(identityCredential: IdentityCredential): BiometricPrompt.CryptoObject {
+            return BiometricPrompt.CryptoObject(identityCredential)
+        }
+    }
+
+    /** Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28). */
+    @RequiresApi(Build.VERSION_CODES.P)
+    private object Api28Impl {
+        /**
+         * Creates an instance of the framework class
+         * [android.hardware.biometrics.BiometricPrompt.CryptoObject] from the given cipher.
+         *
+         * @param cipher The cipher object to be wrapped.
+         * @return An instance of [android.hardware.biometrics.BiometricPrompt.CryptoObject].
+         */
+        fun create(cipher: Cipher): BiometricPrompt.CryptoObject {
+            return BiometricPrompt.CryptoObject(cipher)
+        }
+
+        /**
+         * Creates an instance of the framework class
+         * [android.hardware.biometrics.BiometricPrompt.CryptoObject] from the given signature.
+         *
+         * @param signature The signature object to be wrapped.
+         * @return An instance of [android.hardware.biometrics.BiometricPrompt.CryptoObject].
+         */
+        fun create(signature: Signature): BiometricPrompt.CryptoObject {
+            return BiometricPrompt.CryptoObject(signature)
+        }
+
+        /**
+         * Creates an instance of the framework class
+         * [android.hardware.biometrics.BiometricPrompt.CryptoObject] from the given MAC.
+         *
+         * @param mac The MAC object to be wrapped.
+         * @return An instance of [android.hardware.biometrics.BiometricPrompt.CryptoObject].
+         */
+        fun create(mac: Mac): BiometricPrompt.CryptoObject {
+            return BiometricPrompt.CryptoObject(mac)
+        }
+    }
+}
diff --git a/development/jspecify_update.py b/development/jspecify_update.py
new file mode 100755
index 0000000..06e8fe5
--- /dev/null
+++ b/development/jspecify_update.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+
+import os
+import subprocess
+import sys
+from subprocess import CompletedProcess
+
+
+def usage():
+    print("""Usage: jspecify_update.py <dir>
+This script updates all projects in a directory to use JSpecify annotations instead of AndroidX
+nullness annotations. If no directory is provided, uses the root frameworks/support dir.
+""")
+    sys.exit(1)
+
+
+def run_gradle_task(gradle_path: str, task: str, args: list[str] | None = None) -> \
+        CompletedProcess[bytes]:
+    """Runs a Gradle task ans returns the result."""
+    cmd = [gradle_path, task] + (args or [])
+    return subprocess.run(cmd, capture_output=True)
+
+
+def update_annotations(dir_path: str, gradle_path: str) -> bool:
+    """If the project in dir_path has a lint task, runs lintFix to shift all AndroidX nullness
+    annotations to type-use position. Returns whether any updates were applied."""
+    tasks_result = run_gradle_task(gradle_path, "tasks")
+    if "lintFix" not in str(tasks_result.stdout):
+        print(f"Lint task does not exist for {dir_path}")
+        return False
+
+    fix_result = run_gradle_task(gradle_path, "lintFix", ["-Pandroidx.useJSpecifyAnnotations=true"])
+    return fix_result.returncode != 0
+
+
+def update_imports_for_file(filepath: str) -> bool:
+    """Replaces AndroidX nullness annotation imports with JSpecify imports. Returns whether any
+    updates were needed."""
+    with open(filepath, "r") as f:
+        lines = f.readlines()
+
+    replacements = {
+        "import androidx.annotation.NonNull": "import org.jspecify.annotations.NonNull;\n",
+        "import androidx.annotation.Nullable": "import org.jspecify.annotations.Nullable;\n"
+    }
+    updated_count = 0
+    for i, line in enumerate(lines):
+        for target, replacement in replacements.items():
+            if target in line:
+                lines[i] = replacement
+                updated_count += 1
+                break
+
+        if updated_count == len(replacements):
+            break
+
+    replaced = updated_count > 0
+    if replaced:
+        with open(filepath, "w") as f:
+            f.writelines(lines)
+    return replaced
+
+
+def reformat_files(gradle_path: str) -> None:
+    """Runs the java formatter to fix imports for the specified files."""
+    run_gradle_task(gradle_path, "javaFormat", ["--fix-imports-only"]),
+
+
+def update_imports(dir_path: str, gradle_path: str) -> bool:
+    """For each java file in the directory, replaces the AndroidX nullness imports with JSpecify
+    imports and runs the java formatter to correct the new import order."""
+    java_files = []
+    for sub_dir_path, _, filenames in os.walk(dir_path):
+        for filename in filenames:
+            (_, ext) = os.path.splitext(filename)
+            if ext == ".java":
+                file_path = os.path.join(sub_dir_path, filename)
+                if update_imports_for_file(file_path):
+                    java_files.append(file_path)
+
+    if java_files:
+        reformat_files(gradle_path)
+    return java_files
+
+
+def add_jspecify_dependency(build_gradle_path: str) -> None:
+    """Adds a JSpecify dependency to the build file."""
+    with open(build_gradle_path, "r") as f:
+        lines = f.readlines()
+
+    jspecify_dependency = "    api(libs.jspecify)\n"
+    if jspecify_dependency in lines:
+        print(f"JSpecify dependency already present for {build_gradle_path}")
+        return
+
+    dependencies_start = None
+    for i in range(len(lines)):
+        line = lines[i]
+        if line.startswith("dependencies {"):
+            dependencies_start = i
+            break
+
+    if not dependencies_start:
+        print(f"No dependencies block found for {build_gradle_path}")
+        return
+
+    lines.insert(dependencies_start + 1, "    api(libs.jspecify)\n")
+    with open(build_gradle_path, "w") as f:
+        f.writelines(lines)
+
+
+def process_dir(dir_path: str, root_dir_path: str) -> None:
+    """Updates the directory to use JSpecify annotations."""
+    print(f"Processing {dir_path}")
+    os.chdir(dir_path)
+    gradle_path = os.path.join(root_dir_path, "gradlew")
+    if update_annotations(dir_path, gradle_path):
+        print(f"Lint fixes applied in {dir_path}")
+        if update_imports(dir_path, gradle_path):
+            add_jspecify_dependency(os.path.join(dir_path, "build.gradle"))
+
+
+def main(start_dir: str | None) -> None:
+    # Location of this script: under support/development
+    script_path = os.path.realpath(__file__)
+    # Move up to the support dir
+    support_dir = os.path.dirname(os.path.dirname(script_path))
+
+    # Search the specified directory, or the support dir if there wasn't one
+    if not start_dir:
+        start_dir = support_dir
+    else:
+        start_dir = os.path.abspath(start_dir)
+    for dir_path, _, filenames in os.walk(start_dir):
+        if dir_path == support_dir:
+            continue
+        if "build.gradle" in filenames:
+            process_dir(dir_path, support_dir)
+
+
+if __name__ == '__main__':
+    if len(sys.argv) == 1:
+        main(None)
+    elif len(sys.argv) == 2:
+        main(sys.argv[1])
+    else:
+        usage()
diff --git a/development/update_gradle.sh b/development/update_gradle.sh
new file mode 100755
index 0000000..b400ac4
--- /dev/null
+++ b/development/update_gradle.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+set -e
+
+# Check if the user has provided the version as an argument
+if [ -z "$1" ]; then
+  echo "Error: No version provided. Usage: $0 <gradle-version>"
+  exit 1
+fi
+
+VERSION="$1"
+DEST_DIR="../../tools/external/gradle"
+WRAPPER_FILES=("gradle/wrapper/gradle-wrapper.properties" "playground-common/gradle/wrapper/gradle-wrapper.properties")
+
+BASE_URL="https://services.gradle.org/distributions"
+ZIP_FILE="gradle-${VERSION}-bin.zip"
+SHA_FILE="${ZIP_FILE}.sha256"
+
+# Function to check if a URL is valid by checking the HTTP status code
+check_url() {
+  local url="$1"
+
+  echo "Checking URL: $url"
+
+  http_status=$(curl -L --silent --head --write-out "%{http_code}" --output /dev/null "$url")
+
+  if [ "$http_status" -ne 200 ]; then
+    echo "Error: URL returned status code $http_status. The file doesn't exist at: $url"
+    exit 1
+  else
+    echo "URL is valid: $url"
+  fi
+}
+
+check_url "$BASE_URL/$ZIP_FILE"
+check_url "$BASE_URL/$SHA_FILE"
+
+echo "Cleaning destination directory: $DEST_DIR"
+rm -rf "$DEST_DIR"/*
+mkdir -p "$DEST_DIR"
+
+echo "Downloading Gradle ${VERSION}..."
+curl -Lo "$DEST_DIR/$ZIP_FILE" "$BASE_URL/$ZIP_FILE"
+curl -Lo "$DEST_DIR/$SHA_FILE" "$BASE_URL/$SHA_FILE"
+
+GRADLE_SHA256SUM=$(cat "$DEST_DIR/$SHA_FILE")
+
+echo "Downloaded Gradle ${VERSION} with SHA256: $GRADLE_SHA256SUM"
+
+update_gradle_wrapper_properties() {
+  local file="$1"
+  echo "Updating $file..."
+
+  if [ "$(uname)" = "Darwin" ]; then
+    sed -i '' "
+      s|distributionUrl=.*tools/external/gradle/.*|distributionUrl=../../../../tools/external/gradle/${ZIP_FILE}|;
+      s|distributionUrl=https\\\://services.gradle.org/distributions/.*|distributionUrl=https\\\://services.gradle.org/distributions/${ZIP_FILE}|;
+      s|distributionSha256Sum=.*|distributionSha256Sum=${GRADLE_SHA256SUM}|
+    " "$file"
+  else
+    sed -i "
+      s|distributionUrl=.*tools/external/gradle/.*|distributionUrl=../../../../tools/external/gradle/${ZIP_FILE}|;
+      s|distributionUrl=https\\\://services.gradle.org/distributions/.*|distributionUrl=https\\\://services.gradle.org/distributions/${ZIP_FILE}|;
+      s|distributionSha256Sum=.*|distributionSha256Sum=${GRADLE_SHA256SUM}|
+    " "$file"
+  fi
+
+  echo "Updated $file."
+}
+
+for file in "${WRAPPER_FILES[@]}"; do
+  update_gradle_wrapper_properties "$file"
+done
+
+echo "Gradle binary downloaded, and the wrapper properties updated successfully!"
+
+echo "Testing the setup with './gradlew bOS --dry-run'..."
+if ./gradlew bOS --dry-run; then
+  echo "Download and setup successful!"
+  echo "You can now upload changes in $(pwd) and $DEST_DIR to Gerrit!"
+fi
diff --git a/glance/glance-appwidget-multiprocess/api/current.txt b/glance/glance-appwidget-multiprocess/api/current.txt
index 6714fbe..6252f24 100644
--- a/glance/glance-appwidget-multiprocess/api/current.txt
+++ b/glance/glance-appwidget-multiprocess/api/current.txt
@@ -23,8 +23,7 @@
   public abstract class MultiProcessGlanceAppWidget extends androidx.glance.appwidget.GlanceAppWidget {
     ctor public MultiProcessGlanceAppWidget();
     ctor public MultiProcessGlanceAppWidget(optional @LayoutRes int errorUiLayout);
-    method public androidx.glance.appwidget.multiprocess.MultiProcessConfig? getMultiProcessConfig();
-    property public androidx.glance.appwidget.multiprocess.MultiProcessConfig? multiProcessConfig;
+    method public androidx.glance.appwidget.multiprocess.MultiProcessConfig? getMultiProcessConfig(android.content.Context context);
   }
 
 }
diff --git a/glance/glance-appwidget-multiprocess/api/restricted_current.txt b/glance/glance-appwidget-multiprocess/api/restricted_current.txt
index 6714fbe..6252f24 100644
--- a/glance/glance-appwidget-multiprocess/api/restricted_current.txt
+++ b/glance/glance-appwidget-multiprocess/api/restricted_current.txt
@@ -23,8 +23,7 @@
   public abstract class MultiProcessGlanceAppWidget extends androidx.glance.appwidget.GlanceAppWidget {
     ctor public MultiProcessGlanceAppWidget();
     ctor public MultiProcessGlanceAppWidget(optional @LayoutRes int errorUiLayout);
-    method public androidx.glance.appwidget.multiprocess.MultiProcessConfig? getMultiProcessConfig();
-    property public androidx.glance.appwidget.multiprocess.MultiProcessConfig? multiProcessConfig;
+    method public androidx.glance.appwidget.multiprocess.MultiProcessConfig? getMultiProcessConfig(android.content.Context context);
   }
 
 }
diff --git a/glance/glance-appwidget-multiprocess/src/main/kotlin/androidx/glance/appwidget/multiprocess/MultiProcessGlanceAppWidget.kt b/glance/glance-appwidget-multiprocess/src/main/kotlin/androidx/glance/appwidget/multiprocess/MultiProcessGlanceAppWidget.kt
index 415fb48..1afa3a9 100644
--- a/glance/glance-appwidget-multiprocess/src/main/kotlin/androidx/glance/appwidget/multiprocess/MultiProcessGlanceAppWidget.kt
+++ b/glance/glance-appwidget-multiprocess/src/main/kotlin/androidx/glance/appwidget/multiprocess/MultiProcessGlanceAppWidget.kt
@@ -40,29 +40,34 @@
     @LayoutRes internal open val errorUiLayout: Int = R.layout.glance_error_layout,
 ) : GlanceAppWidget(errorUiLayout) {
     /**
-     * Override [multiProcessConfig] to provide a [androidx.work.multiprocess.RemoteWorkerService]
-     * that runs in the same process as the [androidx.glance.appwidget.GlanceAppWidgetReceiver] that
-     * this is attached to.
+     * Override [getMultiProcessConfig] to provide a
+     * [androidx.work.multiprocess.RemoteWorkerService] that runs in the same process as the
+     * [androidx.glance.appwidget.GlanceAppWidgetReceiver] that this is attached to.
      *
      * If null, then this widget will be run with normal WorkManager, i.e. the same behavior as
      * GlanceAppWidget.
      */
-    public open val multiProcessConfig: MultiProcessConfig? = null
+    public open fun getMultiProcessConfig(context: Context): MultiProcessConfig? = null
 
-    @get:RestrictTo(Scope.LIBRARY_GROUP)
-    final override val components: GlanceComponents?
-        get() = multiProcessConfig?.toGlanceComponents()
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    final override fun getComponents(context: Context): GlanceComponents? =
+        getMultiProcessConfig(context)?.toGlanceComponents()
 
-    @get:RestrictTo(Scope.LIBRARY_GROUP)
-    protected final override val sessionManager: SessionManager
-        get() = if (multiProcessConfig != null) RemoteSessionManager else GlanceSessionManager
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    protected final override fun getSessionManager(context: Context): SessionManager =
+        if (getMultiProcessConfig(context) != null) {
+            RemoteSessionManager
+        } else {
+            GlanceSessionManager
+        }
 
     @RestrictTo(Scope.LIBRARY_GROUP)
     protected final override fun createAppWidgetSession(
+        context: Context,
         id: AppWidgetId,
         options: Bundle?
     ): AppWidgetSession {
-        return multiProcessConfig?.let { config ->
+        return getMultiProcessConfig(context)?.let { config ->
             RemoteAppWidgetSession(this, config.remoteWorkerService, id, options)
         } ?: AppWidgetSession(this, id, options)
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
index a9c4fc1..4f228ac 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
@@ -187,7 +187,7 @@
                     layoutConfig.addLayout(root),
                     DpSize.Unspecified,
                     receiver,
-                    widget.components ?: GlanceComponents.getDefault(context),
+                    widget.getComponents(context) ?: GlanceComponents.getDefault(context),
                 )
             if (shouldPublish) {
                 appWidgetManager.updateAppWidget(id.appWidgetId, rv)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
index 2f2358e..6dc3f1e 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -58,8 +58,8 @@
 abstract class GlanceAppWidget(
     @LayoutRes internal open val errorUiLayout: Int = R.layout.glance_error_layout,
 ) {
-    @get:RestrictTo(Scope.LIBRARY_GROUP)
-    protected open val sessionManager: SessionManager = GlanceSessionManager
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    protected open fun getSessionManager(context: Context): SessionManager = GlanceSessionManager
 
     /**
      * Override this function to provide the Glance Composable.
@@ -140,7 +140,7 @@
      */
     internal suspend fun deleted(context: Context, appWidgetId: Int) {
         val glanceId = AppWidgetId(appWidgetId)
-        sessionManager.runWithLock { closeSession(glanceId.toSessionKey()) }
+        getSessionManager(context).runWithLock { closeSession(glanceId.toSessionKey()) }
         try {
             onDelete(context, glanceId)
         } catch (cancelled: CancellationException) {
@@ -163,9 +163,9 @@
     ) {
         Tracing.beginGlanceAppWidgetUpdate()
         val glanceId = AppWidgetId(appWidgetId)
-        sessionManager.runWithLock {
+        getSessionManager(context).runWithLock {
             if (!isSessionRunning(context, glanceId.toSessionKey())) {
-                startSession(context, createAppWidgetSession(glanceId, options))
+                startSession(context, createAppWidgetSession(context, glanceId, options))
                 return@runWithLock
             }
             val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
@@ -184,7 +184,8 @@
         options: Bundle? = null,
     ) {
         val glanceId = AppWidgetId(appWidgetId)
-        sessionManager.getOrCreateAppWidgetSession(context, glanceId, options) { session ->
+        getSessionManager(context).getOrCreateAppWidgetSession(context, glanceId, options) { session
+            ->
             session.runLambda(actionKey)
         }
     }
@@ -200,7 +201,8 @@
             return
         }
         val glanceId = AppWidgetId(appWidgetId)
-        sessionManager.getOrCreateAppWidgetSession(context, glanceId, options) { session ->
+        getSessionManager(context).getOrCreateAppWidgetSession(context, glanceId, options) { session
+            ->
             session.updateAppWidgetOptions(options)
         }
     }
@@ -247,7 +249,7 @@
         block: suspend SessionManagerScope.(AppWidgetSession) -> Unit
     ) = runWithLock {
         if (!isSessionRunning(context, glanceId.toSessionKey())) {
-            startSession(context, createAppWidgetSession(glanceId, options))
+            startSession(context, createAppWidgetSession(context, glanceId, options))
         }
         val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
         block(session)
@@ -259,11 +261,15 @@
      *
      * If null, then the default components will be used.
      */
-    @get:RestrictTo(Scope.LIBRARY_GROUP) open val components: GlanceComponents? = null
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    open fun getComponents(context: Context): GlanceComponents? = null
 
     @RestrictTo(Scope.LIBRARY_GROUP)
-    protected open fun createAppWidgetSession(id: AppWidgetId, options: Bundle? = null) =
-        AppWidgetSession(this@GlanceAppWidget, id, options)
+    protected open fun createAppWidgetSession(
+        context: Context,
+        id: AppWidgetId,
+        options: Bundle? = null
+    ) = AppWidgetSession(this@GlanceAppWidget, id, options)
 }
 
 @RestrictTo(Scope.LIBRARY_GROUP) data class AppWidgetId(val appWidgetId: Int) : GlanceId
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3e2af46..1739056 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -150,6 +150,7 @@
 findbugs = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" }
 firebaseAppindexing = { module = "com.google.firebase:firebase-appindexing", version = "19.2.0" }
 freemarker = { module = "org.freemarker:freemarker", version = "2.3.31"}
+googlejavaformat = { module = "com.google.googlejavaformat:google-java-format", version = "1.22.0" }
 googletest = { module = "com.android.ndk.thirdparty:googletest", version = "1.11.0-beta-1" }
 hamcrestCore = { module = "org.hamcrest:hamcrest-core", version.ref = "hamcrestCore" }
 hiltAndroid = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
@@ -165,6 +166,7 @@
 jcodecJavaSe = { module = "org.jcodec:jcodec-javase", version.ref = "jcodec" }
 json = { module = "org.json:json", version = "20180813" }
 jsoup = { module = "org.jsoup:jsoup", version = "1.16.2" }
+jspecify = { module = "org.jspecify:jspecify", version = "1.0.0" }
 jsqlparser = { module = "com.github.jsqlparser:jsqlparser", version = "3.1" }
 jsr250 = { module = "javax.annotation:javax.annotation-api", version = "1.2" }
 junit = { module = "junit:junit", version = "4.13.2" }
@@ -205,7 +207,7 @@
 kotlinCoroutinesRx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "kotlinCoroutines" }
 kotlinDaemonEmbeddable = { module = "org.jetbrains.kotlin:kotlin-daemon-embeddable", version.ref = "kotlin"  }
 kotlinKlibCommonizer = { module = "org.jetbrains.kotlin:kotlin-klib-commonizer", version.ref = "kotlin"  }
-kotlinMetadataJvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.9.0" }
+kotlinMetadataJvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" }
 kotlinSerializationCore = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinSerialization" }
 kotlinSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
 kotlinSerializationJsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinSerialization" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 5d0eb4f..9922876 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -58,7 +58,7 @@
          <trust group="androidx[.]annotation" version="1\.9\.0-beta[0-9][1-9]" regex="true" reason="New versions, not yet signed"/>
          <trust group="androidx[.]annotation" version="1\.[0-7]\..*" regex="true" reason="Old versions, before signing"/>
          <trust group="androidx[.]collection" version="1\.5\.0-alpha[0-9][1-9]" regex="true" reason="Old versions, before signing"/>
-         <trust group="androidx[.]collection" version="1\.[0-3]\..*" regex="true" reason="Old versions, before signing"/>
+         <trust group="androidx[.]collection" version="1\.[0-4]\..*" regex="true" reason="Old versions, before signing"/>
       </trusted-artifacts>
       <trusted-keys>
          <trusted-key id="00089EE8C3AFA95A854D0F1DF800DD0933ECF7F7" group="com.google.guava"/>
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9bd15e6..b5f8900 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.10-bin.zip
-distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a
+distributionUrl=../../../../tools/external/gradle/gradle-8.10.2-bin.zip
+distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt
new file mode 100644
index 0000000..6b6973d
--- /dev/null
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes
+
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class PolygonValidationTest {
+    private val pentagonPoints =
+        floatArrayOf(
+            0.2f,
+            0.0f,
+            0.8f,
+            0.0f,
+            1.0f,
+            0.6f,
+            0.5f,
+            1.0f,
+            0.0f,
+            0.6f,
+        )
+
+    private val reverseOrientedPentagonPoints =
+        floatArrayOf(
+            0.2f,
+            0.0f,
+            0.0f,
+            0.6f,
+            0.5f,
+            1.0f,
+            1.0f,
+            0.6f,
+            0.8f,
+            0.0f,
+        )
+
+    @Test fun doesNotFixValidSharpPolygon() = staysUnchanged(RoundedPolygon(5))
+
+    @Test
+    fun doesNotFixValidRoundPolygon() =
+        staysUnchanged(RoundedPolygon(5, rounding = CornerRounding(0.5f)))
+
+    @Test
+    fun fixesAntiClockwiseOrientedPolygon() {
+        val valid = RoundedPolygon(pentagonPoints)
+
+        val broken = RoundedPolygon(reverseOrientedPentagonPoints)
+
+        fixes(broken, valid)
+    }
+
+    @Test
+    fun fixesAntiClockwiseOrientedRoundedPolygon() {
+        val valid = RoundedPolygon(pentagonPoints, rounding = CornerRounding(0.5f))
+
+        val broken = RoundedPolygon(reverseOrientedPentagonPoints, rounding = CornerRounding(0.5f))
+
+        fixes(broken, valid)
+    }
+
+    private fun staysUnchanged(polygon: RoundedPolygon) {
+        val copy = RoundedPolygon(polygon)
+        val fixedPolygon = PolygonValidator.fix(polygon)
+
+        assertTrue(polygon === fixedPolygon)
+        assertEquals(copy, polygon)
+    }
+
+    private fun fixes(broken: RoundedPolygon, expected: RoundedPolygon) {
+        val fixed = PolygonValidator.fix(broken)
+
+        assertFalse(broken == fixed)
+        assertPolygonsEqualish(expected, fixed)
+    }
+}
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
index 8c66d27..f39f85e 100644
--- a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
@@ -72,6 +72,26 @@
     }
 }
 
+internal fun assertFeaturesEqualish(expected: Feature, actual: Feature) {
+    assertCubicListsEqualish(expected.cubics, actual.cubics)
+    assertEquals(expected::class, actual::class)
+
+    if (expected is Feature.Corner && actual is Feature.Corner) {
+        pointsEqualish(expected.vertex, actual.vertex)
+        pointsEqualish(expected.roundedCenter, actual.roundedCenter)
+        assertEquals(expected.convex, actual.convex)
+    }
+}
+
+internal fun assertPolygonsEqualish(expected: RoundedPolygon, actual: RoundedPolygon) {
+    assertCubicListsEqualish(expected.cubics, actual.cubics)
+
+    assertEquals(expected.features.size, actual.features.size)
+    for (i in expected.features.indices) {
+        assertFeaturesEqualish(expected.features[i], actual.features[i])
+    }
+}
+
 internal fun assertPointGreaterish(expected: Point, actual: Point) {
     assertTrue(actual.x >= expected.x - Epsilon)
     assertTrue(actual.y >= expected.y - Epsilon)
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
index 8a2135d..501e598 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
@@ -24,6 +24,8 @@
 internal abstract class Feature(val cubics: List<Cubic>) {
     internal abstract fun transformed(f: PointTransformer): Feature
 
+    internal abstract fun reversed(): Feature
+
     /**
      * Edges have only a list of the cubic curves which make up the edge. Edges lie between corners
      * and have no vertex or concavity; the curves are simply straight lines (represented by Cubic
@@ -41,6 +43,16 @@
                 }
             )
 
+        override fun reversed(): Edge {
+            val reversedCubics = mutableListOf<Cubic>()
+
+            for (i in cubics.lastIndex downTo 0) {
+                reversedCubics.add(cubics[i].reverse())
+            }
+
+            return Edge(reversedCubics)
+        }
+
         override fun toString(): String = "Edge"
     }
 
@@ -72,6 +84,18 @@
             )
         }
 
+        override fun reversed(): Corner {
+            val reversedCubics = mutableListOf<Cubic>()
+
+            for (i in cubics.lastIndex downTo 0) {
+                reversedCubics.add(cubics[i].reverse())
+            }
+
+            // TODO: b/369320447 - Revert flag negation when [RoundedPolygon] ignores orientation
+            // for setting the flag
+            return Corner(reversedCubics, vertex, roundedCenter, !convex)
+        }
+
         override fun toString(): String {
             return "Corner: vertex=$vertex, center=$roundedCenter, convex=$convex"
         }
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt
new file mode 100644
index 0000000..607a0b1
--- /dev/null
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes
+
+// @TODO: Make class public as soon as all validations are implemented and mention in
+// [RoundedPolygon] constructor
+
+/**
+ * Utility class to fix invalid [RoundedPolygon]s that will otherwise break [Morph]s in one way or
+ * another, as [RoundedPolygon] assumes correct input. Correct input meaning:
+ * - Closed geometry
+ * - Clockwise orientation of points
+ * - No self-intersections
+ * - No holes
+ */
+internal class PolygonValidator() {
+
+    companion object {
+
+        // @TODO: Update docs when other validations are implemented
+        /**
+         * Validates whether this [RoundedPolygon]'s orientation is clockwise and fixes it if
+         * necessary.
+         *
+         * @param polygon The [RoundedPolygon] to validate
+         * @return A new [RoundedPolygon] with fixed orientation, or the same [RoundedPolygon] as
+         *   given when it was already valid
+         */
+        fun fix(polygon: RoundedPolygon): RoundedPolygon {
+            var result = polygon
+
+            debugLog(LOG_TAG) { "Validating polygon..." }
+
+            if (isCWOriented(polygon)) {
+                debugLog(LOG_TAG) { "Passed clockwise validation!" }
+            } else {
+                debugLog(LOG_TAG) { "Polygon is oriented anti-clockwise, fixing orientation..." }
+                result = fixCWOrientation(polygon)
+            }
+
+            return result
+        }
+
+        private fun isCWOriented(polygon: RoundedPolygon): Boolean {
+            var signedArea = 0.0f
+
+            for (i in polygon.cubics.indices) {
+                val cubic = polygon.cubics[i]
+                signedArea += (cubic.anchor1X - cubic.anchor0X) * (cubic.anchor1Y + cubic.anchor0Y)
+            }
+
+            return signedArea < 0
+        }
+
+        private fun fixCWOrientation(polygon: RoundedPolygon): RoundedPolygon {
+            val reversedFeatures = buildList {
+                // Persist first feature to stay a Corner
+                add(polygon.features.first().reversed())
+
+                for (i in polygon.features.lastIndex downTo 1) {
+                    add(polygon.features[i].reversed())
+                }
+            }
+
+            return RoundedPolygon(reversedFeatures, polygon.centerX, polygon.centerY)
+        }
+    }
+}
+
+private const val LOG_TAG = "PolygonValidation"
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
index 7181326..e93e881 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
@@ -21,19 +21,57 @@
 import androidx.activity.result.ActivityResultCaller
 import androidx.annotation.Sampled
 import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.HealthConnectFeatures
 import androidx.health.connect.client.contracts.ExerciseRouteRequestContract
+import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
+import androidx.health.connect.client.permission.HealthPermission
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND
 import androidx.health.connect.client.readRecord
 import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.connect.client.records.ExerciseRouteResult
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.HeartRateRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.request.ReadRecordsRequest
 import androidx.health.connect.client.time.TimeRangeFilter
 import java.time.Instant
 
+@OptIn(ExperimentalFeatureAvailabilityApi::class)
+@Sampled
+suspend fun ReadSkinTemperatureRecord(
+    healthConnectClient: HealthConnectClient,
+    startTime: Instant,
+    endTime: Instant
+) {
+    if (
+        healthConnectClient.features.getFeatureStatus(
+            HealthConnectFeatures.FEATURE_SKIN_TEMPERATURE
+        ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
+    ) {
+        if (
+            healthConnectClient.permissionController
+                .getGrantedPermissions()
+                .contains(HealthPermission.getReadPermission(SkinTemperatureRecord::class))
+        ) {
+            val response =
+                healthConnectClient.readRecords(
+                    ReadRecordsRequest<SkinTemperatureRecord>(
+                        timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
+                    )
+                )
+            for (skinTemperatureRecord in response.records) {
+                // Process each skin temperature record
+            }
+        } else {
+            // Permission hasn't been granted. Request permission to read skin temperature.
+        }
+    } else {
+        // Feature is not available. It is not possible to read skin temperature.
+    }
+}
+
 @Sampled
 suspend fun ReadStepsRange(
     healthConnectClient: HealthConnectClient,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
index bef39e4..1c3d1ae 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
@@ -17,6 +17,7 @@
 
 import androidx.annotation.IntDef
 import androidx.annotation.RestrictTo
+import androidx.health.connect.client.HealthConnectFeatures
 import androidx.health.connect.client.records.metadata.Metadata
 import androidx.health.connect.client.units.Temperature
 import androidx.health.connect.client.units.TemperatureDelta
@@ -28,6 +29,11 @@
  * Captures the skin temperature of a user. Each record can represent a series of measurements of
  * temperature differences.
  *
+ * The ability to insert or read this record type is dependent on the version of HealthConnect
+ * installed on the device. To check if available: call [HealthConnectFeatures.getFeatureStatus] and
+ * pass [HealthConnectFeatures.FEATURE_SKIN_TEMPERATURE] as an argument. See further down for an
+ * example on how to read skin temperature.
+ *
  * @param startTime Start time of the record.
  * @param startZoneOffset User experienced zone offset at [startTime], or null if unknown. Providing
  *   these will help history aggregations results stay consistent should user travel. Queries with
@@ -48,6 +54,7 @@
  * @param metadata set of common metadata associated with the written record.
  * @throws IllegalArgumentException if [startTime] > [endTime] or [deltas] are not within the record
  *   time range or baseline is not within [MIN_TEMPERATURE], [MAX_TEMPERATURE].
+ * @sample androidx.health.connect.client.samples.ReadSkinTemperatureRecord
  */
 class SkinTemperatureRecord(
     override val startTime: Instant,
diff --git a/ink/ink-authoring/api/current.txt b/ink/ink-authoring/api/current.txt
index 8bee50d..df33139 100644
--- a/ink/ink-authoring/api/current.txt
+++ b/ink/ink-authoring/api/current.txt
@@ -14,11 +14,14 @@
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs);
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs, optional @AttrRes int defStyleAttr);
     method public void addFinishedStrokesListener(androidx.ink.authoring.InProgressStrokesFinishedListener listener);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? prediction);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId, optional androidx.ink.strokes.StrokeInputBatch prediction);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? event);
     method public void eagerInit();
+    method public void finishStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void finishStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public java.util.Map<androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.Stroke> getFinishedStrokes();
     method public androidx.test.espresso.idling.CountingIdlingResource? getInProgressStrokeCounter();
@@ -31,6 +34,9 @@
     method public void setMaskPath(android.graphics.Path?);
     method public void setMotionEventToViewTransform(android.graphics.Matrix);
     method public void setRendererFactory(kotlin.jvm.functions.Function0<? extends androidx.ink.rendering.android.canvas.CanvasStrokeRenderer>);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform, optional android.graphics.Matrix strokeToWorldTransform);
     method public androidx.ink.authoring.InProgressStrokeId startStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.brush.Brush brush);
     property public final androidx.test.espresso.idling.CountingIdlingResource? inProgressStrokeCounter;
     property public final android.graphics.Path? maskPath;
diff --git a/ink/ink-authoring/api/restricted_current.txt b/ink/ink-authoring/api/restricted_current.txt
index 8bee50d..df33139 100644
--- a/ink/ink-authoring/api/restricted_current.txt
+++ b/ink/ink-authoring/api/restricted_current.txt
@@ -14,11 +14,14 @@
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs);
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs, optional @AttrRes int defStyleAttr);
     method public void addFinishedStrokesListener(androidx.ink.authoring.InProgressStrokesFinishedListener listener);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? prediction);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId, optional androidx.ink.strokes.StrokeInputBatch prediction);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? event);
     method public void eagerInit();
+    method public void finishStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void finishStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public java.util.Map<androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.Stroke> getFinishedStrokes();
     method public androidx.test.espresso.idling.CountingIdlingResource? getInProgressStrokeCounter();
@@ -31,6 +34,9 @@
     method public void setMaskPath(android.graphics.Path?);
     method public void setMotionEventToViewTransform(android.graphics.Matrix);
     method public void setRendererFactory(kotlin.jvm.functions.Function0<? extends androidx.ink.rendering.android.canvas.CanvasStrokeRenderer>);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform, optional android.graphics.Matrix strokeToWorldTransform);
     method public androidx.ink.authoring.InProgressStrokeId startStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.brush.Brush brush);
     property public final androidx.test.espresso.idling.CountingIdlingResource? inProgressStrokeCounter;
     property public final android.graphics.Path? maskPath;
diff --git a/ink/ink-authoring/build.gradle b/ink/ink-authoring/build.gradle
index 4180c02..3b003528 100644
--- a/ink/ink-authoring/build.gradle
+++ b/ink/ink-authoring/build.gradle
@@ -32,6 +32,7 @@
     androidMain {
       dependencies {
         implementation("androidx.collection:collection:1.4.3")
+        implementation("androidx.graphics:graphics-core:1.0.0")
         implementation("androidx.fragment:fragment-ktx:1.3.0")
         implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
         implementation(project(":core:core"))
@@ -41,7 +42,6 @@
         implementation(project(":ink:ink-brush"))
         implementation(project(":ink:ink-strokes"))
         implementation(project(":ink:ink-rendering"))
-        implementation(project(":graphics:graphics-core"))
       }
     }
 
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
index 07894b6..39211bc 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
@@ -300,10 +300,10 @@
                         osDetectsEvent = 321_000_000
                         // The clock ticked once to record this time.
                         strokesViewGetsAction = 777_001
+                        // And twice more for this one.
+                        canvasFrontBufferStrokesRenderHelperData.finishesDrawCalls = 777_003
                         // And once more for this one.
-                        canvasFrontBufferStrokesRenderHelperData.finishesDrawCalls = 777_002
-                        // And once more for this one.
-                        estimatedPixelPresentationTime = 777_003
+                        estimatedPixelPresentationTime = 777_004
                     }
                 )
             )
@@ -815,11 +815,11 @@
                         osDetectsEvent = 333_000_000
                         // The clock ticked once to get this time.
                         strokesViewGetsAction = 334_000_001
-                        // And twice for this one - once on the UI thread and once on the render
+                        // And thrice for this one - twice on the UI thread and once on the render
                         // thread.
-                        canvasFrontBufferStrokesRenderHelperData.finishesDrawCalls = 334_000_003
-                        // And once more for this one.
-                        estimatedPixelPresentationTime = 334_000_004
+                        canvasFrontBufferStrokesRenderHelperData.finishesDrawCalls = 334_000_004
+                        // And once more to get the estimated time.
+                        estimatedPixelPresentationTime = 334_000_005
                     }
                 )
             )
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
index 4ae50d0..5996a1b 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
@@ -75,8 +75,8 @@
      * This must be set to its desired value before the first call to [startStroke] or [eagerInit].
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     @Deprecated("Prefer to allow the underlying implementation details to be chosen automatically.")
     public var useHighLatencyRenderHelper: Boolean = false
 
@@ -89,8 +89,8 @@
      * This must be set to its desired value before the first call to [startStroke] or [eagerInit].
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     @Deprecated("Prefer to allow the underlying implementation details to be chosen automatically.")
     public var useNewTPlusRenderHelper: Boolean = false
 
@@ -104,8 +104,8 @@
      * If handoff is ever needed as soon as safely possible, call [requestHandoff].
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     public var handoffDebounceTimeMs: Long = 0L
         @UiThread
         set(value) {
@@ -130,13 +130,13 @@
      */
     // Needed on both property and on getter for AndroidX build, but the Kotlin compiler doesn't
     // like it on the getter so suppress its complaint.
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @ExperimentalInkCustomBrushApi
     @get:ExperimentalInkCustomBrushApi
     @set:ExperimentalInkCustomBrushApi
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public var textureBitmapStore: TextureBitmapStore = TextureBitmapStore { null }
         set(value) {
             check(!isInitialized()) { "Cannot set textureBitmapStore after initialization." }
@@ -224,15 +224,15 @@
      * minimize the amount of computation in this callback, and should also avoid allocations (since
      * allocation may trigger the garbage collector).
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     // Needed on both property and on getter for AndroidX build, but the Kotlin compiler doesn't
     // like it on the getter so suppress its complaint.
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @ExperimentalLatencyDataApi
     @get:ExperimentalLatencyDataApi
     @set:ExperimentalLatencyDataApi
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     public var latencyDataCallback: LatencyDataCallback? = null
 
     private val renderHelperCallback =
@@ -399,11 +399,30 @@
     }
 
     /**
-     * Start building a stroke with the [event] data for [pointerId].
+     * Start building a stroke using a particular pointer within a [MotionEvent]. This would
+     * typically be followed by many calls to [addToStroke], and the sequence would end with a call
+     * to either [finishStroke] or [cancelStroke].
      *
-     * @param event The first [MotionEvent] as part of a Stroke's input data, typically an
-     *   ACTION_DOWN.
-     * @param pointerId The id of the relevant pointer in the [event].
+     * In most circumstances, prefer to use this function over [startStroke] that accepts a
+     * [StrokeInput].
+     *
+     * For optimum performance, it is strongly recommended to call [View.requestUnbufferedDispatch]
+     * using [event] and the [View] that generated [event] alongside calling this function. When
+     * requested this way, unbuffered dispatch mode will automatically end when the gesture is
+     * complete.
+     *
+     * @param event The first [MotionEvent] as part of a Stroke's input data, typically one with a
+     *   [MotionEvent.getActionMasked] value of [MotionEvent.ACTION_DOWN] or
+     *   [MotionEvent.ACTION_POINTER_DOWN], but not restricted to those action types.
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param brush Brush specification for the stroke being started. Note that the overall scaling
+     *   factor of [motionEventToWorldTransform] and [strokeToWorldTransform] combined should be
+     *   related to the value of [Brush.epsilon] - in general, the larger the combined
+     *   `motionEventToStrokeTransform` scaling factor is, the smaller on screen the stroke units
+     *   are, so [Brush.epsilon] should be a larger quantity of stroke units to maintain a similar
+     *   screen size.
      * @param motionEventToWorldTransform The matrix that transforms [event] coordinates into the
      *   client app's "world" coordinates, which typically is defined by how a client app's document
      *   is panned/zoomed/rotated. This defaults to the identity matrix, in which case the world
@@ -412,22 +431,18 @@
      *   density (e.g. scaled by 1 / [android.util.DisplayMetrics.density]) and any pan/zoom/rotate
      *   gestures that have been applied to the "camera" which portrays the "world" on the device
      *   screen. This matrix must be invertible.
-     * @param strokeToWorldTransform An optional matrix that transforms this stroke into the client
-     *   app's "world" coordinates, which allows the coordinates of the stroke to be defined in
-     *   something other than world coordinates. Defaults to the identity matrix, in which case the
-     *   stroke coordinate space is the same as world coordinate space. This matrix must be
-     *   invertible.
-     * @param brush Brush specification for the stroke being started. Note that if
-     *   [motionEventToWorldTransform] and [strokeToWorldTransform] combine to a [MotionEvent] to
-     *   stroke coordinates transform that scales stroke coordinate units to be very different in
-     *   size than screen pixels, then it is recommended to update the value of [Brush.epsilon] to
-     *   reflect that.
-     * @return The Stroke ID of the stroke being built, later used to identify which stroke is being
-     *   added to, finished, or canceled.
+     * @param strokeToWorldTransform Allows an object-specific (stroke-specific) coordinate space to
+     *   be defined in relation to the caller's "world" coordinate space. This defaults to the
+     *   identity matrix, which is typical for many use cases at the time of stroke construction. In
+     *   typical use cases, stroke coordinates and world coordinates may start to differ from one
+     *   another after stroke creation as a particular stroke is manipulated within the world, e.g.
+     *   it may be moved, scaled, or rotated relative to other content within an app's document.
+     *   This matrix must be invertible.
+     * @return The [InProgressStrokeId] of the stroke being built, later used to identify which
+     *   stroke is being updated with [addToStroke] or ended with [finishStroke] or [cancelStroke].
      * @throws IllegalArgumentException if [motionEventToWorldTransform] or [strokeToWorldTransform]
      *   is not invertible.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     @JvmOverloads
     public fun startStroke(
         event: MotionEvent,
@@ -475,31 +490,44 @@
     }
 
     /**
-     * Start building a stroke with the provided [input].
+     * Start building a stroke with the provided [input]. This would typically be followed by many
+     * calls to [addToStroke], and the sequence would end with a call to either [finishStroke] or
+     * [cancelStroke].
+     *
+     * In most circumstances, the [startStroke] overload that accepts a [MotionEvent] is more
+     * convenient. However, this overload using a [StrokeInput] is available for cases where the
+     * input data may not come directly from a [MotionEvent], such as receiving events over a
+     * network connection.
+     *
+     * If there is a way to request unbuffered dispatch from the source of the input data used here,
+     * equivalent to [View.requestUnbufferedDispatch] for unbuffered [MotionEvent] data, then be
+     * sure to request it for optimal performance.
      *
      * @param input The [StrokeInput] that started a stroke.
      * @param brush Brush specification for the stroke being started. Note that if stroke coordinate
-     *   units (the [StrokeInput.x] and [StrokeInput.y] fields of [input] are scaled to be very
+     *   units (the [StrokeInput.x] and [StrokeInput.y] fields of [input]) are scaled to be very
      *   different in size than screen pixels, then it is recommended to update the value of
      *   [Brush.epsilon] to reflect that.
-     * @return The Stroke ID of the stroke being built, later used to identify which stroke is being
-     *   added to, finished, or canceled.
+     * @return The [InProgressStrokeId] of the stroke being built, later used to identify which
+     *   stroke is being updated with [addToStroke] or ended with [finishStroke] or [cancelStroke].
      */
     public fun startStroke(input: StrokeInput, brush: Brush): InProgressStrokeId =
         inProgressStrokesManager.startStroke(input, brush)
 
     /**
-     * Add [event] data for [pointerId] to already started stroke with [strokeId].
+     * Add input data, from a particular pointer within a [MotionEvent], to an existing stroke.
      *
-     * @param event the next [MotionEvent] as part of a Stroke's input data, typically an
-     *   ACTION_MOVE.
-     * @param pointerId the id of the relevant pointer in the [event].
-     * @param strokeId the Stroke that is to be built upon with [event].
-     * @param prediction optional predicted [MotionEvent] containing predicted inputs between event
-     *   and the time of the next frame, as generated by
-     *   [androidx.input.motionprediction.MotionEventPredictor.predict].
+     * @param event The next [MotionEvent] as part of a stroke's input data, typically one with
+     *   [MotionEvent.getActionMasked] of [MotionEvent.ACTION_MOVE].
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be built upon.
+     * @param prediction Predicted [MotionEvent] containing predicted inputs between [event] and the
+     *   time of the next frame. This value typically comes from
+     *   [androidx.input.motionprediction.MotionEventPredictor.predict]. It is technically optional,
+     *   but it is strongly recommended to achieve the best performance.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     @JvmOverloads
     public fun addToStroke(
         event: MotionEvent,
@@ -515,12 +543,13 @@
         )
 
     /**
-     * Add [inputs] to already started stroke with [strokeId].
+     * Add input data from a [StrokeInputBatch] to an existing stroke.
      *
-     * @param inputs the next [StrokeInputBatch] to be added to the stroke.
-     * @param strokeId the Stroke that is to be built upon with [inputs].
-     * @param prediction optional [StrokeInputBatch] containing predicted inputs after this portion
-     *   of the stroke.
+     * @param inputs The next [StrokeInputBatch] to be added to the stroke.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be built upon.
+     * @param prediction Predicted [StrokeInputBatch] containing predicted inputs between [inputs]
+     *   and the time of the next frame. This can technically be empty, but it is strongly
+     *   recommended for it to be non-empty to achieve the best performance.
      */
     @JvmOverloads
     public fun addToStroke(
@@ -558,13 +587,22 @@
     }
 
     /**
-     * Complete the building of a stroke.
+     * Complete the building of a stroke, with the last input data coming from a particular pointer
+     * of a [MotionEvent].
      *
-     * @param event the last [MotionEvent] as part of a stroke, typically an ACTION_UP.
-     * @param pointerId the id of the relevant pointer in the [event].
-     * @param strokeId the stroke that is to be finished with the latest event.
+     * When the stroke no longer needs to be rendered by this [InProgressStrokesView] and can
+     * instead be rendered anywhere in the [View] hierarchy using [CanvasStrokeRenderer], the
+     * resulting [Stroke] object will be passed to the [InProgressStrokesFinishedListener] instances
+     * registered with this [InProgressStrokesView] using [addFinishedStrokesListener].
+     *
+     * @param event The last [MotionEvent] as part of a stroke's input data, typically one with
+     *   [MotionEvent.getActionMasked] of [MotionEvent.ACTION_UP] or
+     *   [MotionEvent.ACTION_POINTER_UP], but can also be other actions.
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be finished.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun finishStroke(
         event: MotionEvent,
         pointerId: Int,
@@ -572,18 +610,36 @@
     ): Unit = inProgressStrokesManager.finishStroke(event, pointerId, strokeId)
 
     /**
-     * Complete the building of a stroke.
+     * Complete the building of a stroke, with the last input data coming from a [StrokeInput].
      *
-     * @param input the last [StrokeInput] in the stroke.
-     * @param strokeId the stroke that is to be finished with the latest event.
+     * @param input The last [StrokeInput] in the stroke.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be finished.
      */
     public fun finishStroke(input: StrokeInput, strokeId: InProgressStrokeId): Unit =
         inProgressStrokesManager.finishStroke(input, strokeId)
 
     /**
-     * Cancel the building of a stroke.
+     * Cancel the building of a stroke. It will no longer be visible within this
+     * [InProgressStrokesView], and no completed [Stroke] object will come through
+     * [InProgressStrokesFinishedListener].
      *
-     * @param strokeId the stroke to cancel.
+     * This is typically done for one of three reasons:
+     * 1. A [MotionEvent] with [MotionEvent.getActionMasked] of [MotionEvent.ACTION_CANCEL]. This
+     *    tends to be when an entire gesture has been canceled, for example when a parent [View]
+     *    uses [android.view.ViewGroup.onInterceptTouchEvent] to intercept and handle the gesture
+     *    itself.
+     * 2. A [MotionEvent] with [MotionEvent.getFlags] containing [MotionEvent.FLAG_CANCELED]. This
+     *    tends to be when the system has detected an unintentional touch, such as from the user
+     *    resting their palm on the screen while writing or drawing, after some events from that
+     *    unintentional pointer have already been delivered.
+     * 3. An app's business logic reinterprets a gesture previously used for inking as something
+     *    else, and the earlier inking may be seen as unintentional. For example, an app that uses
+     *    single-pointer gestures for inking and dual-pointer gestures for pan/zoom/rotate will
+     *    start inking when the first pointer goes down, but when the second pointer goes down it
+     *    may want to cancel the stroke from the first pointer rather than leave the small ink marks
+     *    on the screen.
+     *
+     * @param strokeId The [InProgressStrokeId] of the stroke to be canceled.
      * @param event The [MotionEvent] that led to this cancellation, if applicable.
      */
     @JvmOverloads
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
index b06a42e..06633b8 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
@@ -933,16 +933,10 @@
                     it,
                 )
             }
-            // TODO: b/287041801 - Don't necessarily always immediately [updateShape] after
-            // [enqueueInputs].
-            updateShape(getNanoTime() / 1_000_000L - strokeState.startEventTimeMillis).onFailure {
-                // TODO(b/306361370): Throw here once input is more sanitized.
-                Log.w(
-                    InProgressStrokesManager::class.simpleName,
-                    "Error during InProgressStroke.updateShape",
-                    it,
-                )
-            }
+            // Rather than updating the shape immediately, we enqueue the inputs and wait to update
+            // the
+            // shape until we have handled all the inputs in threadSharedState.inputActions. This is
+            // being done to reduce that amount of updateShape calls.
         }
         action.realInputs.clear()
         action.predictedInputs.clear()
@@ -985,8 +979,8 @@
                         it,
                     )
                 }
-            // TODO: b/287041801 - Don't necessarily always immediately [updateShape] after
-            // [enqueueInputs].
+            // We update the finished stroke immediately after enqueueing because we know we are not
+            // going to be receiving any other inputs.
             strokeState.inProgressStroke
                 .updateShape(getNanoTime() / 1_000_000L - strokeState.startEventTimeMillis)
                 .onFailure {
@@ -1210,7 +1204,22 @@
             handleAction(nextInputAction)
             renderThreadState.handledActions.add(nextInputAction)
         }
-
+        val nowMillis = getNanoTime() / 1_000_000L
+        for (strokeState in renderThreadState.toDrawStrokes.values) {
+            val inProgressStroke = strokeState.inProgressStroke
+            if (inProgressStroke.getNeedsUpdate()) {
+                inProgressStroke
+                    .updateShape(nowMillis - strokeState.startEventTimeMillis)
+                    .onFailure {
+                        // TODO(b/306361370): Throw here once input is more sanitized.
+                        Log.w(
+                            InProgressStrokesManager::class.simpleName,
+                            "Error during InProgressStroke.updateShape after handleAction",
+                            it,
+                        )
+                    }
+            }
+        }
         if (inProgressStrokesRenderHelper.contentsPreservedBetweenDraws) {
             // The updated region for each stroke must be drawn into for all strokes, not just
             // itself, to
diff --git a/ink/ink-brush/api/current.txt b/ink/ink-brush/api/current.txt
index 7599e2b..8b67e7a 100644
--- a/ink/ink-brush/api/current.txt
+++ b/ink/ink-brush/api/current.txt
@@ -70,6 +70,17 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ExperimentalInkCustomBrushApi {
   }
 
+  public final class InputToolType {
+    field public static final androidx.ink.brush.InputToolType.Companion Companion;
+    field public static final androidx.ink.brush.InputToolType MOUSE;
+    field public static final androidx.ink.brush.InputToolType STYLUS;
+    field public static final androidx.ink.brush.InputToolType TOUCH;
+    field public static final androidx.ink.brush.InputToolType UNKNOWN;
+  }
+
+  public static final class InputToolType.Companion {
+  }
+
   public final class StockBrushes {
     method public static androidx.ink.brush.BrushFamily getHighlighterLatest();
     method public static androidx.ink.brush.BrushFamily getHighlighterV1();
diff --git a/ink/ink-brush/api/restricted_current.txt b/ink/ink-brush/api/restricted_current.txt
index 7599e2b..8b67e7a 100644
--- a/ink/ink-brush/api/restricted_current.txt
+++ b/ink/ink-brush/api/restricted_current.txt
@@ -70,6 +70,17 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ExperimentalInkCustomBrushApi {
   }
 
+  public final class InputToolType {
+    field public static final androidx.ink.brush.InputToolType.Companion Companion;
+    field public static final androidx.ink.brush.InputToolType MOUSE;
+    field public static final androidx.ink.brush.InputToolType STYLUS;
+    field public static final androidx.ink.brush.InputToolType TOUCH;
+    field public static final androidx.ink.brush.InputToolType UNKNOWN;
+  }
+
+  public static final class InputToolType.Companion {
+  }
+
   public final class StockBrushes {
     method public static androidx.ink.brush.BrushFamily getHighlighterLatest();
     method public static androidx.ink.brush.BrushFamily getHighlighterV1();
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
index d0c8086..467ed12 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
@@ -25,7 +25,6 @@
  * The type of input tool used in producing [androidx.ink.strokes.StrokeInput], used by
  * [BrushBehavior] to define when a behavior is applicable.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
 @UsedByNative
 public class InputToolType
 private constructor(
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt
index 27523e0..cef896e 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt
@@ -119,19 +119,49 @@
                         listOf(
                             predictionFadeOutBehavior,
                             BrushBehavior(
-                                source = BrushBehavior.Source.NORMALIZED_PRESSURE,
-                                target = BrushBehavior.Target.SIZE_MULTIPLIER,
-                                sourceValueRangeLowerBound = 0f,
+                                BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE,
+                                BrushBehavior.Target.SIZE_MULTIPLIER,
+                                sourceValueRangeLowerBound = 3f,
+                                sourceValueRangeUpperBound = 0f,
+                                targetModifierRangeLowerBound = 1f,
+                                targetModifierRangeUpperBound = 0.75f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source.NORMALIZED_DIRECTION_Y,
+                                BrushBehavior.Target.SIZE_MULTIPLIER,
+                                sourceValueRangeLowerBound = 0.45f,
+                                sourceValueRangeUpperBound = 0.65f,
+                                targetModifierRangeLowerBound = 1.0f,
+                                targetModifierRangeUpperBound = 1.17f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 25L,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source
+                                    .INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED,
+                                BrushBehavior.Target.SIZE_MULTIPLIER,
+                                sourceValueRangeLowerBound = -80f,
+                                sourceValueRangeUpperBound = -230f,
+                                targetModifierRangeLowerBound = 1.0f,
+                                targetModifierRangeUpperBound = 1.25f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 25L,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source.NORMALIZED_PRESSURE,
+                                BrushBehavior.Target.SIZE_MULTIPLIER,
+                                sourceValueRangeLowerBound = 0.8f,
                                 sourceValueRangeUpperBound = 1f,
-                                targetModifierRangeLowerBound = 0.05f,
-                                targetModifierRangeUpperBound = 1f,
-                                sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
-                                responseCurve = EasingFunction.Predefined.LINEAR,
-                                responseTimeMillis = 40L,
+                                targetModifierRangeLowerBound = 1.0f,
+                                targetModifierRangeUpperBound = 1.5f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 30L,
                                 enabledToolTypes = setOf(InputToolType.STYLUS),
                             ),
                         )
-                )
+                ),
+            inputModel = BrushFamily.SPRING_MODEL,
         )
 
     /**
@@ -156,17 +186,61 @@
         BrushFamily(
             tip =
                 BrushTip(
-                    scaleX = 0.05f,
+                    scaleX = 0.25f,
                     scaleY = 1f,
-                    cornerRounding = 0.11f,
+                    cornerRounding = 0.3f,
                     rotation = Angle.degreesToRadians(150f),
-                    behaviors = listOf(predictionFadeOutBehavior),
-                )
+                    behaviors =
+                        listOf(
+                            predictionFadeOutBehavior,
+                            BrushBehavior(
+                                BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE,
+                                BrushBehavior.Target.CORNER_ROUNDING_OFFSET,
+                                sourceValueRangeLowerBound = 0f,
+                                sourceValueRangeUpperBound = 1f,
+                                targetModifierRangeLowerBound = 0.3f,
+                                targetModifierRangeUpperBound = 1f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 15L,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source.DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE,
+                                BrushBehavior.Target.CORNER_ROUNDING_OFFSET,
+                                sourceValueRangeLowerBound = 0f,
+                                sourceValueRangeUpperBound = 1f,
+                                targetModifierRangeLowerBound = 0.3f,
+                                targetModifierRangeUpperBound = 1f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 15L,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source.DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE,
+                                BrushBehavior.Target.OPACITY_MULTIPLIER,
+                                sourceValueRangeLowerBound = 0f,
+                                sourceValueRangeUpperBound = 3f,
+                                targetModifierRangeLowerBound = 1.1f,
+                                targetModifierRangeUpperBound = 1f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 15L,
+                            ),
+                            BrushBehavior(
+                                BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE,
+                                BrushBehavior.Target.OPACITY_MULTIPLIER,
+                                sourceValueRangeLowerBound = 0f,
+                                sourceValueRangeUpperBound = 3f,
+                                targetModifierRangeLowerBound = 1.1f,
+                                targetModifierRangeUpperBound = 1f,
+                                BrushBehavior.OutOfRange.CLAMP,
+                                responseTimeMillis = 15L,
+                            ),
+                        ),
+                ),
+            inputModel = BrushFamily.SPRING_MODEL,
         )
 
     /**
-     * Version 1 of a chisel-tip brush that is intended for highlighting text in a document (when
-     * used with a translucent brush color).
+     * The latest version of a chisel-tip brush that is intended for highlighting text in a document
+     * (when used with a translucent brush color).
      *
      * The behavior of this [BrushFamily] may change in future releases, as it always points to the
      * latest version of the pressure pen.
diff --git a/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt b/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt
index 7531e6b..cdd4106 100644
--- a/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt
+++ b/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt
@@ -24,7 +24,7 @@
  * Use this to annotate methods, fields, and types that are referenced by name from native code to
  * prevent them from being removed as unused.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
 @MustBeDocumented
 @Target(
     AnnotationTarget.FUNCTION,
diff --git a/ink/ink-rendering/api/current.txt b/ink/ink-rendering/api/current.txt
index adc06c7..9635393 100644
--- a/ink/ink-rendering/api/current.txt
+++ b/ink/ink-rendering/api/current.txt
@@ -3,6 +3,11 @@
 
   public interface CanvasStrokeRenderer {
     method public static androidx.ink.rendering.android.canvas.CanvasStrokeRenderer create();
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method @Px public default int strokeModifiedRegionOutsetPx();
     field public static final androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.Companion Companion;
   }
 
diff --git a/ink/ink-rendering/api/restricted_current.txt b/ink/ink-rendering/api/restricted_current.txt
index e06360d..fa05dd5 100644
--- a/ink/ink-rendering/api/restricted_current.txt
+++ b/ink/ink-rendering/api/restricted_current.txt
@@ -3,6 +3,11 @@
 
   public interface CanvasStrokeRenderer {
     method public static androidx.ink.rendering.android.canvas.CanvasStrokeRenderer create();
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method @Px public default int strokeModifiedRegionOutsetPx();
     field public static final androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.Companion Companion;
   }
 
diff --git a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTest.kt b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTest.kt
index 14c0c57..5bda1ea 100644
--- a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTest.kt
+++ b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTest.kt
@@ -77,7 +77,8 @@
                                 BrushFamily(BrushTip(opacityMultiplier = 1.0F)),
                                 TestColors.COBALT_BLUE.withAlpha(0.4),
                             ),
-                            INPUTS_TWIST,
+                            // TODO: b/369408056 - change this back to INPUTS_TWIST
+                            INPUTS_ZIGZAG,
                         ),
                     ),
                     Pair(
@@ -109,7 +110,8 @@
                                 ),
                                 TestColors.RED,
                             ),
-                            INPUTS_TWIST,
+                            // TODO: b/369408056 - change this back to INPUTS_TWIST
+                            INPUTS_ZIGZAG,
                         ),
                     ),
                     // TODO: b/330528190 - Add row for atlased textures
@@ -157,7 +159,8 @@
                                 ),
                                 TestColors.AVOCADO_GREEN,
                             ),
-                            INPUTS_TWIST,
+                            // TODO: b/369408056 - change this back to INPUTS_TWIST
+                            INPUTS_ZIGZAG,
                         ),
                     ),
                     // TODO: b/274461578 - Add row for winding textures
@@ -475,14 +478,6 @@
                 .addOrThrow(InputToolType.UNKNOWN, x = 5F, y = 90F, elapsedTimeMillis = 250)
                 .asImmutable()
 
-        val INPUTS_TWIST =
-            MutableStrokeInputBatch()
-                .addOrThrow(InputToolType.UNKNOWN, x = 0F, y = 0F, elapsedTimeMillis = 100)
-                .addOrThrow(InputToolType.UNKNOWN, x = 80F, y = 100F, elapsedTimeMillis = 150)
-                .addOrThrow(InputToolType.UNKNOWN, x = 0F, y = 100F, elapsedTimeMillis = 200)
-                .addOrThrow(InputToolType.UNKNOWN, x = 80F, y = 0F, elapsedTimeMillis = 250)
-                .asImmutable()
-
         fun brush(
             family: BrushFamily = StockBrushes.markerLatest,
             @ColorInt color: Int = TestColors.BLACK,
@@ -564,7 +559,8 @@
                 )
             val paint = BrushPaint(listOf(textureLayer))
             val brush = brush(BrushFamily(paint = paint), color, size = 30f)
-            return finishedInProgressStroke(brush, INPUTS_TWIST)
+            // TODO: b/369408056 - change this back to INPUTS_TWIST
+            return finishedInProgressStroke(brush, INPUTS_ZIGZAG)
         }
 
         fun textureBlendedStroke(blendMode: BlendMode): InProgressStroke {
diff --git a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
index 3592f4b..d4647a3 100644
--- a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
+++ b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
@@ -48,12 +48,13 @@
 
     private inner class StrokeView(context: Context) : View(context) {
 
+        // TODO: b/369408056 - Change back to twist-style input points
         private val inputs =
             MutableStrokeInputBatch()
                 .addOrThrow(InputToolType.UNKNOWN, x = 0F, y = 0F, elapsedTimeMillis = 100)
-                .addOrThrow(InputToolType.UNKNOWN, x = 80F, y = 100F, elapsedTimeMillis = 150)
-                .addOrThrow(InputToolType.UNKNOWN, x = 0F, y = 100F, elapsedTimeMillis = 200)
-                .addOrThrow(InputToolType.UNKNOWN, x = 80F, y = 0F, elapsedTimeMillis = 250)
+                .addOrThrow(InputToolType.UNKNOWN, x = 40F, y = 40F, elapsedTimeMillis = 150)
+                .addOrThrow(InputToolType.UNKNOWN, x = 0F, y = 70F, elapsedTimeMillis = 200)
+                .addOrThrow(InputToolType.UNKNOWN, x = 30F, y = 100F, elapsedTimeMillis = 250)
                 .asImmutable()
 
         // Pink twist stroke.
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
index 80d188e..bcb4e99 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
@@ -32,6 +32,39 @@
 /**
  * Renders strokes to a [Canvas].
  *
+ * Instead of calling the [draw] methods here directly, it may be simpler to pass an instance of
+ * [CanvasStrokeRenderer] to [androidx.ink.rendering.android.view.ViewStrokeRenderer] and use it to
+ * calculate transform matrix values.
+ *
+ * An example of how to use [CanvasStrokeRenderer.draw] directly:
+ * ```
+ * class MyView {
+ *   // Update these according to app business logic, and call `MyView.invalidate()`
+ *   val worldToViewTransform = Matrix() // Call e.g. `setScale(2F)` to zoom in 2x
+ *   val strokesWithTransforms = mutableMapOf<Stroke, Matrix>()
+ *
+ *   private val strokeToViewTransform = Matrix() // reusable scratch object
+ *   private val renderer = CanvasStrokeRenderer.create()
+ *
+ *   fun onDraw(canvas: Canvas) {
+ *     for ((stroke, strokeToWorldTransform) in strokesWithTransforms) {
+ *       // Combine worldToViewTransform (drawing surface being panned/zoomed/rotated) with
+ *       // strokeToWorldTransform (stroke itself being moved/scaled/rotated within the drawing
+ *       // surface) to get the overall transform of this stroke.
+ *       strokeToViewTransform.set(strokeToWorldTransform)
+ *       strokeToViewTransform.postConcat(worldToViewTransform)
+ *
+ *       canvas.withMatrix(strokeToViewTransform) {
+ *         // If coordinates of MyView are scaled/rotated from screen coordinates, then those
+ *         // scale/rotation values should be multiplied into the strokeToScreenTransform
+ *         // argument to renderer.draw.
+ *         renderer.draw(canvas, stroke, strokeToViewTransform)
+ *       }
+ *     }
+ *   }
+ * }
+ * ```
+ *
  * In almost all cases, a developer should use an implementation of this interface obtained from
  * [CanvasStrokeRenderer.create].
  *
@@ -64,7 +97,6 @@
      * blurry or aliased.
      */
     // TODO: b/353561141 - Reference ComposeStrokeRenderer above once implemented.
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: AffineTransform)
 
     /**
@@ -80,7 +112,6 @@
      * appear blurry or aliased.
      */
     // TODO: b/353561141 - Reference ComposeStrokeRenderer above once implemented.
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: Matrix)
 
     /**
@@ -92,7 +123,6 @@
      * [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may appear
      * blurry or aliased.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
@@ -108,7 +138,6 @@
      * the [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may
      * appear blurry or aliased.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
@@ -125,9 +154,7 @@
      * lowest value that avoids the artifacts, as larger values will be less performant, and effects
      * that rely on larger values will be less compatible with stroke geometry operations.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @Px
-    public fun strokeModifiedRegionOutsetPx(): Int = 3
+    @Px public fun strokeModifiedRegionOutsetPx(): Int = 3
 
     public companion object {
 
diff --git a/input/input-motionprediction/api/1.0.0-beta05.txt b/input/input-motionprediction/api/1.0.0-beta05.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/1.0.0-beta05.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/api/res-1.0.0-beta05.txt b/input/input-motionprediction/api/res-1.0.0-beta05.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/input/input-motionprediction/api/res-1.0.0-beta05.txt
diff --git a/input/input-motionprediction/api/restricted_1.0.0-beta05.txt b/input/input-motionprediction/api/restricted_1.0.0-beta05.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/restricted_1.0.0-beta05.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index 5ad7111..0d7627e 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -72,11 +72,11 @@
 
     private final DVector2 mLastPosition = new DVector2();
     private long mLastSeenEventTime;
-    private long mLastPredictEventTime;
+    private double mLastPredictEventTime;
     private long mDownEventTime;
-    private List<Float> mReportRates = new LinkedList<>();
+    private List<Double> mReportRates = new LinkedList<>();
     private int mExpectedPredictionSampleSize = -1;
-    private float mReportRateMs = 0;
+    private double mReportRateMs = 0;
 
     private final DVector2 mPosition = new DVector2();
     private final DVector2 mVelocity = new DVector2();
@@ -141,10 +141,10 @@
         // to be used as an estimate.
         if (mReportRates != null && mReportRates.size() < 20) {
             if (mLastSeenEventTime > 0) {
-                float dt = eventTime - mLastSeenEventTime;
+                double dt = eventTime - mLastSeenEventTime;
                 mReportRates.add(dt);
-                float sum = 0;
-                for (float rate : mReportRates) {
+                double sum = 0;
+                for (double rate : mReportRates) {
                     sum += rate;
                 }
                 mReportRateMs = sum / mReportRates.size();
@@ -250,16 +250,6 @@
         int predictionTargetInSamples =
                 (int) Math.ceil(predictionTargetMs / mReportRateMs * confidenceFactor);
 
-        // Predict at least as far in time as the previous prediction.
-        // Otherwise, it may appear that the coordinates are going backwards.
-        if (mLastPredictEventTime > mLastSeenEventTime) {
-            int minimumPredictionSampleSize = (int) Math.floor(
-                    (mLastPredictEventTime - mLastSeenEventTime) / mReportRateMs
-            );
-            if (predictionTargetInSamples < minimumPredictionSampleSize) {
-                predictionTargetInSamples = minimumPredictionSampleSize;
-            }
-        }
         if (mExpectedPredictionSampleSize != -1) {
             // Normally this should always be false as confidenceFactor should be less than 1.0
             if (predictionTargetInSamples > mExpectedPredictionSampleSize) {
@@ -272,9 +262,11 @@
             predictionTargetInSamples = Math.max(predictionTargetInSamples, 1);
         }
 
-        long predictedEventTime = mLastSeenEventTime;
-        int i = 0;
-        for (; i < predictionTargetInSamples; i++) {
+        double predictedEventTime = mLastSeenEventTime;
+        double nextPredictedEventTime = mLastSeenEventTime + mReportRateMs;
+        for (int i = 0;
+                i < predictionTargetInSamples || (nextPredictedEventTime <= mLastPredictEventTime);
+                i++) {
             mAcceleration.a1 += mJank.a1 * JANK_INFLUENCE;
             mAcceleration.a2 += mJank.a2 * JANK_INFLUENCE;
             mVelocity.a1 += mAcceleration.a1 * ACCELERATION_INFLUENCE;
@@ -290,8 +282,6 @@
                 mPressure = 1;
             }
 
-            long nextPredictedEventTime = predictedEventTime + Math.round(mReportRateMs);
-
             // Abort prediction if the pen is to be lifted.
             if (mPredictLift
                     && mPressure < 0.1
@@ -310,7 +300,7 @@
                 predictedEvent =
                         MotionEvent.obtain(
                                 mDownEventTime /* downTime */,
-                                nextPredictedEventTime /* eventTime */,
+                                (long) nextPredictedEventTime /* eventTime */,
                                 MotionEvent.ACTION_MOVE /* action */,
                                 1 /* pointerCount */,
                                 pointerProperties /* pointer properties */,
@@ -324,9 +314,13 @@
                                 0 /* source */,
                                 0 /* flags */);
             } else {
-                predictedEvent.addBatch(nextPredictedEventTime, coords, 0);
+                predictedEvent.addBatch((long) nextPredictedEventTime, coords, 0);
             }
+            // Keep track of the last predicted time
             predictedEventTime = nextPredictedEventTime;
+
+            // Prepare for next iteration
+            nextPredictedEventTime += mReportRateMs;
         }
 
         // Store the last predicted time
diff --git a/libraryversions.toml b/libraryversions.toml
index 62d780a..b46cc85 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -15,6 +15,7 @@
 CAMERA = "1.5.0-alpha02"
 CAMERA_PIPE = "1.0.0-alpha01"
 CAMERA_TESTING = "1.0.0-alpha01"
+CAMERA_MEDIA3 = "1.0.0-alpha01"
 CAMERA_VIEWFINDER = "1.4.0-alpha09"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-beta02"
@@ -70,7 +71,7 @@
 GRAPHICS_PATH = "1.0.0-rc01"
 GRAPHICS_SHAPES = "1.0.0-rc01"
 GRIDLAYOUT = "1.1.0-beta02"
-HEALTH_CONNECT = "1.1.0-alpha09"
+HEALTH_CONNECT = "1.1.0-alpha10"
 HEALTH_CONNECT_TESTING_QUARANTINE = "1.0.0-alpha01"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha03"
 HEIFWRITER = "1.1.0-alpha03"
@@ -78,7 +79,7 @@
 HILT_NAVIGATION = "1.2.0-rc01"
 HILT_NAVIGATION_COMPOSE = "1.2.0-rc01"
 INK = "1.0.0-alpha01"
-INPUT_MOTIONPREDICTION = "1.0.0-beta04"
+INPUT_MOTIONPREDICTION = "1.0.0-beta05"
 INSPECTION = "1.0.0"
 INTERPOLATOR = "1.1.0-alpha01"
 JAVASCRIPTENGINE = "1.0.0-beta01"
@@ -154,8 +155,8 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.5.0-alpha02"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha25"
+WEAR_COMPOSE = "1.5.0-alpha03"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha26"
 WEAR_CORE = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
@@ -163,7 +164,7 @@
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
 WEAR_PROTOLAYOUT = "1.3.0-alpha01"
 WEAR_PROTOLAYOUT_MATERIAL3 = "1.0.0-alpha01"
-WEAR_REMOTE_INTERACTIONS = "1.1.0-beta01"
+WEAR_REMOTE_INTERACTIONS = "1.1.0-rc01"
 WEAR_TILES = "1.5.0-alpha01"
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
 WEAR_WATCHFACE = "1.3.0-alpha04"
@@ -193,6 +194,7 @@
 CAMERA = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA" }
 CAMERA_PIPE = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_PIPE", overrideInclude = [ ":camera:camera-camera2-pipe", ":camera:camera-camera2-pipe-integration" ] }
 CAMERA_TESTING = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_TESTING", overrideInclude = [ ":camera:camera-testing" ] }
+CAMERA_MEDIA3 = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_MEDIA3", overrideInclude = [ ":camera:camera-media3-effect" ] }
 CAMERA_VIEWFINDER = { group = "androidx.camera.viewfinder", atomicGroupVersion = "versions.CAMERA_VIEWFINDER" }
 CARDVIEW = { group = "androidx.cardview", atomicGroupVersion = "versions.CARDVIEW" }
 CAR_APP = { group = "androidx.car.app", atomicGroupVersion = "versions.CAR_APP" }
diff --git a/navigation/integration-tests/testapp/build.gradle b/navigation/integration-tests/testapp/build.gradle
index 030aecf..a3b7ee4 100644
--- a/navigation/integration-tests/testapp/build.gradle
+++ b/navigation/integration-tests/testapp/build.gradle
@@ -22,11 +22,22 @@
 
 dependencies {
     implementation(libs.kotlinStdlib)
+    implementation(project(":navigation:navigation-common"))
+    implementation(project(":navigation:navigation-fragment"))
+    implementation(project(":navigation:navigation-runtime"))
+    implementation(project(":navigation:navigation-ui"))
     implementation("androidx.appcompat:appcompat:1.1.0")
-    api(project(":fragment:fragment-ktx"))
-    api(project(":transition:transition-ktx"))
-    implementation(project(":navigation:navigation-fragment-ktx"))
-    implementation(project(":navigation:navigation-ui-ktx"))
+    implementation("androidx.cardview:cardview:1.0.0")
+    implementation("androidx.constraintlayout:constraintlayout:2.0.1")
+    implementation("androidx.core:core:1.13.0")
+    implementation("androidx.customview:customview:1.1.0")
+    implementation("androidx.drawerlayout:drawerlayout:1.1.1")
+    implementation("androidx.fragment:fragment:1.8.3")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
+    implementation("androidx.recyclerview:recyclerview:1.1.0")
+    implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
+    implementation("androidx.transition:transition:1.5.1")
+    implementation("com.google.android.material:material:1.4.0")
     implementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
diff --git a/navigation/navigation-benchmark/build.gradle b/navigation/navigation-benchmark/build.gradle
index ef9ee24..a5a0894 100644
--- a/navigation/navigation-benchmark/build.gradle
+++ b/navigation/navigation-benchmark/build.gradle
@@ -31,14 +31,15 @@
 }
 
 dependencies {
-    androidTestImplementation(projectOrArtifact(":benchmark:benchmark-junit4"))
-    androidTestImplementation(project(":navigation:navigation-runtime"))
+    androidTestImplementation(project(":benchmark:benchmark-common"))
+    androidTestImplementation(project(":benchmark:benchmark-junit4"))
     androidTestImplementation(project(":internal-testutils-navigation"))
+    androidTestImplementation(project(":navigation:navigation-common"))
+    androidTestImplementation(project(":navigation:navigation-runtime"))
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.kotlinStdlib)
 }
 
diff --git a/navigation/navigation-common-lint/build.gradle b/navigation/navigation-common-lint/build.gradle
index b913ca6..46b47fd 100644
--- a/navigation/navigation-common-lint/build.gradle
+++ b/navigation/navigation-common-lint/build.gradle
@@ -33,11 +33,14 @@
 BundleInsideHelper.forInsideLintJar(project)
 
 dependencies {
+    compileOnly(libs.kotlinCompiler)
     compileOnly(libs.kotlinStdlib)
     compileOnly(libs.androidLintPrevApi)
+    compileOnly(libs.intellijCore)
+    compileOnly(libs.uast)
+
     bundleInside(project(":navigation:navigation-lint-common"))
 
-    testImplementation(libs.kotlinStdlib)
     testImplementation(libs.androidLintPrev)
     testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
diff --git a/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
index 5a2f53c..9afee7f 100644
--- a/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
+++ b/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
@@ -29,7 +29,7 @@
  */
 class TypeSafeDestinationMissingAnnotationDetector :
     BaseTypeSafeDestinationMissingAnnotationDetector(
-        methodNames = listOf("navigation", "deepLink"),
+        methodNames = listOf("navigation", "deepLink", "setUriPattern", "navDeepLink"),
         constructorNames =
             listOf(
                 "androidx.navigation.NavDestinationBuilder",
diff --git a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
index cc7d1b4..792e312 100644
--- a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
+++ b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.navigation.lint.common.KEEP_ANNOTATION
 import androidx.navigation.lint.common.NAVIGATION_STUBS
+import androidx.navigation.lint.common.NAV_DEEP_LINK
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -339,5 +340,132 @@
             )
     }
 
+    @Test
+    fun testDeeplinkBuilderSetUriPattern_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+
+                @Keep enum class TestEnum { ONE, TWO }
+                class DeepLink(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavDeepLink.Builder()
+                    builder.setUriPattern<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDeeplinkBuilderSetUriPattern_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                enum class TestEnum { ONE, TWO }
+                class DeepLink(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavDeepLink.Builder()
+                    builder.setUriPattern<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestEnum.kt:5: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testNavDeepLink_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+
+                @Keep enum class TestEnum { ONE, TWO }
+                class DeepLink(val arg: TestEnum)
+
+                 class DeepLink
+
+                fun navigation() {
+                    navDeepLink<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNavDeepLink_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                enum class TestEnum { ONE, TWO }
+                class DeepLink(val arg: TestEnum)
+
+                fun navigation() {
+                    navDeepLink<DeepLink>()
+
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestEnum.kt:5: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
     val STUBS = arrayOf(*NAVIGATION_STUBS, KEEP_ANNOTATION)
 }
diff --git a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
index 32071da..8166828 100644
--- a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
+++ b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
@@ -18,14 +18,19 @@
 
 import androidx.navigation.lint.common.K_SERIALIZER
 import androidx.navigation.lint.common.NAVIGATION_STUBS
+import androidx.navigation.lint.common.NAV_DEEP_LINK
 import androidx.navigation.lint.common.SERIALIZABLE_ANNOTATION
 import androidx.navigation.lint.common.SERIALIZABLE_TEST_CLASS
 import androidx.navigation.lint.common.TEST_CLASS
+import androidx.navigation.lint.common.bytecodeStub
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
 
+@RunWith(JUnit4::class)
 class MissingSerializableAnnotationDetectorTest : LintDetectorTest() {
     override fun getDetector(): Detector = TypeSafeDestinationMissingAnnotationDetector()
 
@@ -580,5 +585,193 @@
             )
     }
 
+    @Test
+    fun testDeeplinkBuilderSetUriPattern_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import kotlinx.serialization.Serializable
+
+                @Serializable class DeepLink
+
+                fun navigation() {
+                    val builder = NavDeepLink.Builder()
+                    builder.setUriPattern<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDeeplinkBuilderSetUriPattern_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                class DeepLink
+
+                fun navigation() {
+                    val builder = NavDeepLink.Builder()
+                    builder.setUriPattern<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expect(
+                """
+src/com/example/DeepLink.kt:5: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class DeepLink
+      ~~~~~~~~
+1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testNavDeepLink_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import kotlinx.serialization.Serializable
+
+                @Serializable class DeepLink
+
+                fun navigation() {
+                    navDeepLink<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNavDeepLink_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                class DeepLink
+
+                fun navigation() {
+                    navDeepLink<DeepLink>()
+
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                NAV_DEEP_LINK
+            )
+            .run()
+            .expect(
+                """
+src/com/example/DeepLink.kt:5: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class DeepLink
+      ~~~~~~~~
+1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testWrongPackage_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.NavGraphBuilder
+                import com.test.navigation
+                import kotlinx.serialization.*
+
+                @Serializable object TestGraph
+                object TestClass
+
+                fun navigation() {
+                   val builder = NavGraphBuilder(route = TestGraph::class)
+
+                    builder.navigation<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                CUSTOM_NAV_GRAPH_BUILDER_EXTENSIONS
+            )
+            .run()
+            .expectClean()
+    }
+
+    private val CUSTOM_NAV_GRAPH_BUILDER_EXTENSIONS =
+        bytecodeStub(
+            "NavGraphBuilderNavigation.kt",
+            "com/test",
+            0x8c23ef1e,
+            """
+package com.test
+
+import androidx.navigation.NavGraphBuilder
+
+// NavGraphBuilder
+inline fun <reified T : Any> NavGraphBuilder.navigation() { }
+        """,
+            """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgsuUSTsxLKcrPTKnQy0ssy0xPLMnM
+                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuKS4uJPz
+                c/VSKxJzC3JShbhDUotL3IsSCzKAcupcHCC5EqCQkDRQC1jcqTQzJyW1yA9u
+                lXeJEoMWAwA5gn4YnAAAAA==
+                """,
+            """
+                com/test/NavGraphBuilderNavigationKt.class:
+                H4sIAAAAAAAA/41RTW/TQBB966SJYwpNU1qSUgoUl6Y94BT11IZIgARYpAGR
+                KJecNrZJNrHXyN5EPfbE/+GGOKCKIz8KMWsqKEVIlbwzb94+z9d+//HlK4AD
+                7DLYXhw5KkiV0+Hzlwn/MH42E6EfJBSKEVcilq9VEYyhPOFz7oRcjpw3w0ng
+                EZtjsORvHUO93ubST2Lhnzh/+MuZj3b7DK1m77B9OeNR6+oJCk01FmnLhMmw
+                OY1VKKQzmUeOkCpIJA8dV6pEyFR4aREWw6o3DrxpJ1adWRi+5QmPAhIy7NT/
+                beMC09VJRlRxEYu4buEabjAs2cJ+b1+cnLm0IFt39Be9faVxGJbb5xMcB4r7
+                XHHijGieo1di2pS0AZWZamDQ5YnQqEHI32c4ODstW2enllE2fjltqsZ6jcC6
+                0WBblkkKo8r2jEaOTv7Vt4+m/vcxy9L2GDb++/6Ppooh/zz2A5q8LWTQmUXD
+                IOnxYUhMpR17POzzROj4nCx1xUhyNUsIW914lnjBC6Evau9mUoko6ItUkPKp
+                lLHKiqTYh4E8sjnLNSygQPEDip6QN/QO9iqlz1jKNT/pFcAmW6DeTRSxTXiN
+                OJPiMpbJkhwVrJB/mKmLdHYytIU6+UPS3KQiqwPkXKy5uOWiipqLddx2sYE7
+                A7AUm7g7wEKqv3sp7md25Se3iEt3PgMAAA==
+                """
+        )
+
     val STUBS = arrayOf(*NAVIGATION_STUBS, SERIALIZABLE_ANNOTATION, K_SERIALIZER)
 }
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index e76f7a7..52382c7 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -46,32 +46,34 @@
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
     api("androidx.savedstate:savedstate-ktx:1.2.1")
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2")
+    api(libs.kotlinStdlib)
+
     implementation("androidx.core:core-ktx:1.1.0")
     implementation("androidx.collection:collection-ktx:1.4.2")
     implementation("androidx.profileinstaller:profileinstaller:1.4.0")
     implementation(libs.kotlinSerializationCore)
 
-    api(libs.kotlinStdlib)
     testImplementation(project(":navigation:navigation-testing"))
     testImplementation("androidx.arch.core:core-testing:2.2.0")
     testImplementation(libs.junit)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.truth)
-    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinCoroutinesCore)
     testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.kotlinTest)
 
-    androidTestImplementation(libs.kotlinTestJunit)
+    androidTestRuntimeOnly(libs.kotlinTestJunit)
+    androidTestRuntimeOnly(libs.testCore)
+
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.espressoCore)
     androidTestImplementation(libs.truth)
     androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.dexmakerMockito)
-    androidTestImplementation(libs.kotlinStdlib)
 
-    lintPublish(project(':navigation:navigation-common-lint'))
+    lintPublish(project(":navigation:navigation-common-lint"))
 }
 
 androidx {
diff --git a/navigation/navigation-compose-lint/build.gradle b/navigation/navigation-compose-lint/build.gradle
index 6c48c02..d321692 100644
--- a/navigation/navigation-compose-lint/build.gradle
+++ b/navigation/navigation-compose-lint/build.gradle
@@ -34,17 +34,19 @@
 dependencies {
     compileOnly(libs.androidLintMinApi)
     compileOnly(libs.kotlinStdlib)
+    compileOnly(libs.intellijCore)
+    compileOnly(libs.uast)
     bundleInside(project(":compose:lint:common"))
     bundleInside(project(":navigation:navigation-lint-common"))
 
+    testRuntimeOnly(libs.kotlinReflect)
+
     testImplementation(project(":compose:lint:common-test"))
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.kotlinReflect)
     testImplementation(libs.kotlinStdlibJdk8)
-    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevApi)
     testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
-    testImplementation(libs.truth)
 }
 
 androidx {
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index af843ef..1d733ed 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -27,37 +27,62 @@
 }
 
 dependencies {
-
-    implementation(libs.kotlinStdlib)
+    api(libs.kotlinStdlib)
     api("androidx.activity:activity-compose:1.8.0")
-    api("androidx.compose.animation:animation:1.7.0-rc01")
-    implementation("androidx.compose.foundation:foundation-layout:1.7.0-rc01")
-    api("androidx.compose.runtime:runtime:1.7.0-rc01")
-    api("androidx.compose.runtime:runtime-saveable:1.7.0-rc01")
-    api("androidx.compose.ui:ui:1.7.0-rc01")
-    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
-    api(project(":navigation:navigation-runtime-ktx"))
+    api("androidx.compose.animation:animation:1.7.2")
+    api("androidx.compose.runtime:runtime:1.7.2")
+    api("androidx.compose.runtime:runtime-saveable:1.7.2")
+    api("androidx.compose.ui:ui:1.7.2")
+    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2")
+    api(project(":navigation:navigation-runtime"))
+    api(project(":navigation:navigation-common"))
+
+    implementation("androidx.activity:activity:1.8.0")
+    implementation("androidx.annotation:annotation:1.8.0")
+    implementation("androidx.compose.animation:animation-core:1.7.2")
+    implementation("androidx.compose.foundation:foundation-layout:1.7.2")
+    implementation("androidx.lifecycle:lifecycle-common:2.8.2")
+    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.8.2")
+    implementation(libs.kotlinCoroutinesCore)
     implementation(libs.kotlinSerializationCore)
 
-    androidTestImplementation(project(":compose:material:material"))
-    androidTestImplementation project(":compose:test-utils")
-    androidTestImplementation project(":compose:ui:ui-tooling")
+    androidTestImplementation("androidx.activity:activity:1.9.2")
+    androidTestImplementation("androidx.collection:collection-ktx:1.4.2")
+    androidTestImplementation("androidx.core:core-ktx:1.13.0")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.8.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.8.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-common:2.8.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel:2.8.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2")
+    androidTestImplementation("androidx.savedstate:savedstate:1.2.1")
     androidTestImplementation(project(":navigation:navigation-testing"))
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
-    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-    androidTestImplementation(project(":lifecycle:lifecycle-common"))
-    androidTestImplementation(project(":lifecycle:lifecycle-common-java8"))
-    androidTestImplementation(project(":lifecycle:lifecycle-livedata-core"))
-    androidTestImplementation(project(":lifecycle:lifecycle-viewmodel"))
-    androidTestImplementation(project(":lifecycle:lifecycle-viewmodel-savedstate"))
-    androidTestImplementation(project(":activity:activity-ktx"))
-    androidTestImplementation("androidx.collection:collection-ktx:1.4.2")
+    androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.truth)
 
+    // Compose test dependencies
+    androidTestImplementation(project(":compose:animation:animation"))
+    androidTestImplementation(project(":compose:animation:animation-core"))
+    androidTestImplementation(project(":compose:foundation:foundation"))
+    androidTestImplementation(project(":compose:runtime:runtime"))
+    androidTestImplementation(project(":compose:runtime:runtime-saveable"))
+    androidTestImplementation(project(":compose:ui:ui"))
+    androidTestImplementation(project(":compose:ui:ui-graphics"))
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-text"))
+    androidTestImplementation(project(":compose:ui:ui-tooling-preview"))
+    androidTestImplementation(project(":compose:ui:ui-unit"))
+    androidTestImplementation(project(":compose:material:material"))
+    androidTestImplementation(project(":compose:test-utils"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
+    androidTestImplementation(project(":compose:ui:ui-tooling"))
+
     lintChecks(project(":navigation:navigation-compose-lint"))
     lintPublish(project(":navigation:navigation-compose-lint"))
 }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle b/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
index 41d1a1a..839752a 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
@@ -36,11 +36,21 @@
     implementation(libs.kotlinSerializationCore)
 
     implementation(project(":compose:integration-tests:demos:common"))
+    implementation(project(":compose:animation:animation"))
     implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:material:material"))
-    implementation("androidx.compose.material:material-icons-core:1.6.7")
+    implementation(project(":compose:runtime:runtime-saveable"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-graphics"))
+    implementation(project(":compose:ui:ui-text"))
+    implementation(project(":compose:ui:ui-unit"))
+    implementation(project(":navigation:navigation-common"))
+    implementation(project(":navigation:navigation-runtime"))
     implementation(project(":navigation:navigation-compose"))
     implementation(project(":navigation:navigation-compose:navigation-compose-samples"))
+    implementation("androidx.compose.material:material-icons-core:1.7.2")
 }
 
 androidx {
diff --git a/navigation/navigation-compose/samples/build.gradle b/navigation/navigation-compose/samples/build.gradle
index 7cdaa05..94e0951 100644
--- a/navigation/navigation-compose/samples/build.gradle
+++ b/navigation/navigation-compose/samples/build.gradle
@@ -36,17 +36,28 @@
 }
 
 dependencies {
-    implementation(libs.kotlinStdlib)
+    api(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
     implementation(project(":compose:animation:animation"))
-    implementation("androidx.compose.foundation:foundation:1.0.1")
-    implementation("androidx.compose.ui:ui-tooling:1.4.0")
+    implementation(project(":compose:animation:animation-core"))
+    implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
+    implementation(project(":compose:runtime:runtime-saveable"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-graphics"))
+    implementation(project(":compose:ui:ui-text"))
+    implementation(project(":compose:ui:ui-tooling-preview"))
+    implementation(project(":compose:ui:ui-unit"))
     implementation(project(":navigation:navigation-compose"))
+    implementation(project(":navigation:navigation-common"))
+    implementation(project(":navigation:navigation-runtime"))
     implementation("androidx.compose.material:material:1.0.1")
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
-    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.0")
-    implementation("androidx.savedstate:savedstate-ktx:1.2.1")
+    implementation("androidx.compose.ui:ui-tooling:1.4.0")
+    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.2")
+    implementation(libs.kotlinSerializationCore)
+    implementation(libs.kotlinSerializationJson)
 }
 
 androidx {
diff --git a/navigation/navigation-dynamic-features-fragment/build.gradle b/navigation/navigation-dynamic-features-fragment/build.gradle
index d294b3c..5882bd7 100644
--- a/navigation/navigation-dynamic-features-fragment/build.gradle
+++ b/navigation/navigation-dynamic-features-fragment/build.gradle
@@ -34,26 +34,30 @@
 }
 
 dependencies {
+    api("androidx.fragment:fragment:1.6.2")
     api(project(":navigation:navigation-dynamic-features-runtime"))
+    api(project(":navigation:navigation-common"))
     api(project(":navigation:navigation-fragment"))
+    api(project(":navigation:navigation-runtime"))
+    api(libs.playFeatureDelivery)
     api(libs.kotlinStdlib)
+
     implementation(libs.kotlinSerializationCore)
+    implementation("androidx.activity:activity:1.7.2")
+    implementation("androidx.core:core-ktx:1.2.0")
+    implementation("androidx.lifecycle:lifecycle-common:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-livedata-core:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
 
-    testImplementation(libs.testCore)
-    testImplementation(libs.testExtJunit)
-    testImplementation(libs.testRunner)
-    testImplementation(libs.junit)
-    testImplementation(libs.mockitoCore4)
-    testImplementation(libs.robolectric)
-    testImplementation(libs.truth)
+    testRuntimeOnly(libs.testCore)
+    testRuntimeOnly(libs.testRunner)
+    testRuntimeOnly(libs.robolectric)
 
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.dexmakerMockito)
-    androidTestImplementation(libs.espressoCore)
-    androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":internal-testutils-runtime"), {
         exclude group: "androidx.fragment", module: "fragment"
diff --git a/navigation/navigation-dynamic-features-runtime/build.gradle b/navigation/navigation-dynamic-features-runtime/build.gradle
index ba25181..d5f44ab 100644
--- a/navigation/navigation-dynamic-features-runtime/build.gradle
+++ b/navigation/navigation-dynamic-features-runtime/build.gradle
@@ -35,28 +35,33 @@
 }
 
 dependencies {
-    api(project(":navigation:navigation-runtime"))
     api(libs.playFeatureDelivery)
+    api("androidx.lifecycle:lifecycle-livedata-core:2.6.2")
+    api(project(":navigation:navigation-common"))
+    api(project(":navigation:navigation-runtime"))
+
+    implementation("androidx.core:core-ktx:1.2.0")
     implementation(libs.kotlinSerializationCore)
 
+    testRuntimeOnly(libs.testCore)
+    testRuntimeOnly(libs.testRunner)
+    testRuntimeOnly(libs.robolectric)
+
     testImplementation(project(":navigation:navigation-testing"))
     testImplementation("androidx.arch.core:core-testing:2.2.0")
-    testImplementation(libs.testCore)
-    testImplementation(libs.testExtJunit)
-    testImplementation(libs.testRunner)
     testImplementation(libs.junit)
     testImplementation(libs.mockitoCore4)
-    testImplementation(libs.robolectric)
     testImplementation(libs.truth)
+    testImplementation(libs.kotlinCoroutinesCore)
     testImplementation(libs.kotlinCoroutinesTest)
 
+    androidTestImplementation("androidx.annotation:annotation:1.7.0")
+    androidTestImplementation(libs.dexmakerMockito)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.dexmakerMockito)
-    androidTestImplementation(libs.espressoCore)
-    androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":internal-testutils-runtime"), {
         exclude group: "androidx.fragment", module: "fragment"
diff --git a/navigation/navigation-fragment-compose/build.gradle b/navigation/navigation-fragment-compose/build.gradle
index 415e042..bbf7940 100644
--- a/navigation/navigation-fragment-compose/build.gradle
+++ b/navigation/navigation-fragment-compose/build.gradle
@@ -33,16 +33,31 @@
 }
 
 dependencies {
-    implementation(libs.kotlinStdlib)
-    api(project(":navigation:navigation-fragment"))
     api("androidx.compose.runtime:runtime:1.5.4")
     api("androidx.compose.ui:ui:1.5.4")
+    api("androidx.fragment:fragment:1.6.2")
+    api(project(":navigation:navigation-common"))
+    api(project(":navigation:navigation-fragment"))
+    api(project(":navigation:navigation-runtime"))
 
-    androidTestImplementation project(":compose:ui:ui-test-junit4")
-    androidTestImplementation project(":compose:material:material")
-    androidTestImplementation project(":compose:test-utils")
-    androidTestImplementation(libs.testRules)
+    implementation(libs.kotlinStdlib)
+    implementation("androidx.annotation:annotation:1.5.0")
+    implementation("androidx.core:core-ktx:1.2.0")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
+
+    androidTestRuntimeOnly(project(":compose:test-utils"))
+
+    androidTestImplementation("androidx.activity:activity:1.7.2")
+    androidTestImplementation("androidx.fragment:fragment-ktx:1.6.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel:2.8.2")
+    androidTestImplementation(project(":compose:material:material"))
+    androidTestImplementation(project(":compose:runtime:runtime"))
+    androidTestImplementation(project(":compose:ui:ui"))
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
+    androidTestImplementation(project(":compose:ui:ui-text"))
     androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.truth)
 }
diff --git a/navigation/navigation-fragment-ktx/build.gradle b/navigation/navigation-fragment-ktx/build.gradle
index b1199e7..db88f98 100644
--- a/navigation/navigation-fragment-ktx/build.gradle
+++ b/navigation/navigation-fragment-ktx/build.gradle
@@ -31,9 +31,6 @@
 
 dependencies {
     api(project(":navigation:navigation-fragment"))
-    api(project(":navigation:navigation-runtime-ktx")) {
-        because 'Mirror navigation-fragment dependency graph for -ktx artifacts'
-    }
 }
 
 androidx {
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index 6c5a89a..0f7a785 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -34,14 +34,29 @@
 
 dependencies {
     api("androidx.fragment:fragment-ktx:1.6.2")
-    api(project(":navigation:navigation-runtime"))
     api("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
+    api(project(":navigation:navigation-common"))
+    api(project(":navigation:navigation-runtime"))
     api(libs.kotlinStdlib)
+
     implementation(libs.kotlinSerializationCore)
+    implementation(libs.kotlinCoroutinesCore)
+    implementation("androidx.activity:activity:1.7.2")
+    implementation("androidx.core:core-ktx:1.8.0")
+    implementation("androidx.lifecycle:lifecycle-common:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-livedata-core:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
+    implementation("androidx.savedstate:savedstate:1.2.1")
+
     androidTestImplementation(project(":navigation:navigation-testing"))
+    androidTestImplementation("androidx.annotation:annotation:1.7.0")
     androidTestImplementation("androidx.fragment:fragment-testing:1.6.2")
+    androidTestImplementation("androidx.fragment:fragment-testing-manifest:1.6.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2")
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testMonitor)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
diff --git a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt
index bd4765c..ba90077 100644
--- a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt
+++ b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt
@@ -25,6 +25,7 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassOwner
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.impl.source.PsiClassReferenceType
 import org.jetbrains.kotlin.psi.KtClassLiteralExpression
@@ -57,15 +58,27 @@
         node: UCallExpression,
         method: PsiMethod
     ) {
-        val receiver = node.receiver?.getExpressionType()?.canonicalText ?: return
-        // get the destination Type
+        val packageName = (method.containingFile as? PsiClassOwner)?.packageName
+        if (
+            packageName != NAVIGATION_PACKAGE_NAME && packageName != NAVIGATION_COMPOSE_PACKAGE_NAME
+        )
+            return
+
+        val receiver = node.receiver?.getExpressionType()?.canonicalText
+
+        // Get the destination Type, but we only want to lint routes from safe args apis.
+        // Such routes come in two possible forms:
+        // 1. route as regular parameter specifically for NavigatorProvider.navigation
+        // 2. reified type parameter for all other destination builder apis
         val kClazzType =
             when {
                 // route as parameter
                 node.methodName == "navigation" &&
                     receiver == "androidx.navigation.NavigatorProvider" -> node.getRouteKClassType()
                 // route as reified Type
-                else -> (node.typeArguments.first() as? PsiClassReferenceType)?.resolve()
+                node.typeArguments.isNotEmpty() ->
+                    (node.typeArguments.first() as? PsiClassReferenceType)?.resolve()
+                else -> null
             } ?: return
 
         checkMissingSerializableAnnotation(kClazzType, context)
@@ -135,6 +148,10 @@
     }
 }
 
+val NAVIGATION_PACKAGE_NAME = "androidx.navigation"
+
+val NAVIGATION_COMPOSE_PACKAGE_NAME = "androidx.navigation.compose"
+
 fun createMissingSerializableAnnotationIssue(
     detectorClass: Class<out BaseTypeSafeDestinationMissingAnnotationDetector>
 ) =
diff --git a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
index ae79751..3ae4392 100644
--- a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
+++ b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
@@ -410,6 +410,72 @@
 """
     )
 
+val NAV_DEEP_LINK =
+    bytecodeStub(
+        "NavDeeplink.kt",
+        "androidx/navigation",
+        0x73e1868d,
+        """
+package androidx.navigation
+
+class NavDeepLink {
+    class Builder {
+        inline fun <reified T : Any> setUriPattern() {}
+    }
+}
+
+inline fun <reified T : Any> navDeepLink(): NavDeepLink = NavDeepLink()
+""",
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg8uESTsxLKcrPTKnQy0ssy0xPLMnM
+                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIK8AIFXFJTC3Iy87KBXE4g
+                1yO/uMS7hEuSizs5P1cvtSIxtyAnVYgLpMoHrEqJQYsBAHVeaUuBAAAA
+                """,
+        """
+                androidx/navigation/NavDeepLink$Builder.class:
+                H4sIAAAAAAAA/41RTW/TQBB9u86nm7ZOoJCUj0IJlPaA0woJiZYiWoRwFQqi
+                IZecNvEStknWyN5EPebED+EfcELigKIe+VGIWTcXegEfZt58vDc7nl+/f/wE
+                8BgbDBtCh3GkwjNfi4nqC6Mi7R+LyUspPzeVHtQPxmoYyjgPxuCdionwh0L3
+                /bfdU9kzeTgMuT2lldlncB5utkvIIucigzxDxnxSCcNm8z9n7DIsJtJ8iNU7
+                YYyMNUNtr/W0eXnq7j4NYqjUVf1j/RKBBQzl5iAyQ6X9N9KIUBhBwnw0cWhn
+                Zk3RGlDvgPJnykYNQuE2w/PZtOLyKne5N5u6vGABd2fT6my6wxvsIHv+NUeZ
+                oxXPWeWNzLpbmE29bJVtUfH1+ZeCldlhqXiLYe0fi+dxkyE/355haV6ipw8e
+                DQz9wMMolAzL1CuPx6OujFuiO6RMpRn1xLAtYmXjebJ4ovpamHFMuBRoLePD
+                oUgSSSdwT6Jx3JOvlO2rvR9ro0ayrRJFxBdaRyZ9WoJtcDqd/TitQJcke4ci
+                3y5EPrv1HYVvafku2dxFEutkS3NchAt4ZSxQlafkJ1Th5B1n728qJ2up1wjZ
+                eYtYSkWcCxHcS7tLFNcJuSnjFm6jhvtpZQ0PyD+j/DIN9jpwApQDVAJcwVWC
+                WAlI+3oHLEEVtQ5yCdwEq4kFCym48QehdXScDAMAAA==
+                """,
+        """
+                androidx/navigation/NavDeepLink.class:
+                H4sIAAAAAAAA/4VQTU/bQBB9s05sYlwItIXQLz5UCdpDHVAlJIqQgKqSpZRK
+                Lcolp028oouddeXdRBzzW/oPeqrUQxX1yI9CHYfcubyZ9/bN6s3c3v35C+A9
+                tgmb0qRlodOb2MixvpJOFya+kOOPSv3oaJMFIELzWo5lnEtzFX/pX6uBC+AR
+                /GNttDsheHtvuhHq8EPUEBBq7ru2hO3OA39/IKx0ssLl2sSflZOpdJI1MRx7
+                HI8qaFQAAmWs3+iKtblL9wk700kUipYIRXM6CcWCaE0nB6JNR+Sd1f/99EVT
+                VM4DquaDs5HOU1USdh8I9XruDNAiLM0fOGH2LnO82nmRKsIyO9XFaNhX5aXs
+                56ysdoqBzLuy1BWfi1FijCrPc2mt4oOE34pROVCfdPW28XVknB6qrraazafG
+                FG4WxmIfgg85X7y6K+NzZvGMA/W3v7HwixuBF4z+TKzhJWN0b0ADIVcPrxhD
+                1jbYu864OZt6hi2uh6wvsjfqwUvwKMFSgmU0ucVKglU87oEsnuBpDzWL0GLN
+                wrdY/w9v8crnPwIAAA==
+                """,
+        """
+                androidx/navigation/NavDeeplinkKt.class:
+                H4sIAAAAAAAA/41SW2sTQRT+zmyayza222o1qZfaNpVWxE1FEE0piCIubivY
+                EJA8TbLbME0yK7uT0Mc8+Xt8ExQk+OiPEs+sPqgP2oc5851zvnOd+fb90xcA
+                D3GHsCl1lCYqOve1nKqBNCrR/rGcPo/jdyOlh69MCUTwzuRU+iOpB/7r3lnc
+                Z6tDWNQ/iSETCdu7e+E/sllSi/D4oP0k/Dtb6/BCsRv/oZRQJhQPlFbmkODs
+                7nWqcLHoooIqz9BQjdPGHy1TQFgJh4nhUf2j2MhIGsmFxHjq8IbIiooVYO7Q
+                AsHOc2VRk1G0T7g3n1Xd+cwVNZFf3ny2vmaFaNKWW57PPFGju6LpvPz6vmxj
+                HvDWLzAstcmWXfrtMe4PDaHwLIliwjLT4uPJuBenbdkbsWU1TPpy1JGpsvov
+                Y+VEDbQ0k5Sxe5JM0n78QllH/c1EGzWOOypTzHyqdWLyNjLsQ6CAfGqvjgUU
+                Wb+dfxjBDQGu0/qMytuPuPTBbgSbLIvsEbzsLcbVHJexhGXWtnNOiU8jRxvY
+                4fsRczzOvdKFE2A1wOUAV7AW4CquBaih3gVlWMf1LgoZFjLcyHAzw60fGWE6
+                NbwCAAA=
+                """
+    )
+
 val TEST_NAV_HOST =
     bytecodeStub(
         "TestNavHost.kt",
diff --git a/navigation/navigation-runtime-ktx/build.gradle b/navigation/navigation-runtime-ktx/build.gradle
index 1d67a1c6..7ccfe8a 100644
--- a/navigation/navigation-runtime-ktx/build.gradle
+++ b/navigation/navigation-runtime-ktx/build.gradle
@@ -31,9 +31,6 @@
 
 dependencies {
     api(project(":navigation:navigation-runtime"))
-    api(project(":navigation:navigation-common-ktx")) {
-        because 'Mirror navigation-runtime dependency graph for -ktx artifacts'
-    }
 }
 
 androidx {
diff --git a/navigation/navigation-runtime-lint/build.gradle b/navigation/navigation-runtime-lint/build.gradle
index f7c323c..8baa413 100644
--- a/navigation/navigation-runtime-lint/build.gradle
+++ b/navigation/navigation-runtime-lint/build.gradle
@@ -35,11 +35,16 @@
 dependencies {
     compileOnly(libs.androidLintMinApi)
     compileOnly(libs.kotlinStdlib)
+    compileOnly(libs.androidToolsCommon)
+    compileOnly(libs.intellijCore)
+    compileOnly(libs.uast)
+    bundleInside(project(":navigation:navigation-common-lint"))
     bundleInside(project(":navigation:navigation-lint-common"))
 
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevApi)
     testImplementation(libs.androidLintPrevTests)
+    testImplementation(libs.intellijCore)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/navigation/navigation-runtime-truth/build.gradle b/navigation/navigation-runtime-truth/build.gradle
index e250034..581b3f0 100644
--- a/navigation/navigation-runtime-truth/build.gradle
+++ b/navigation/navigation-runtime-truth/build.gradle
@@ -33,12 +33,14 @@
     api(project(":navigation:navigation-runtime"))
     api(libs.truth)
     api(libs.kotlinStdlib)
+
+    implementation(project(":navigation:navigation-common"))
+
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
-    androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 9acb9fd..90bfe60 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -33,32 +33,41 @@
 }
 
 dependencies {
+    api(libs.kotlinStdlib)
+    api(libs.kotlinCoroutinesCore)
     api(project(":navigation:navigation-common"))
     api("androidx.activity:activity-ktx:1.7.1")
+    api("androidx.core:core-ktx:1.8.0")
+    api("androidx.lifecycle:lifecycle-common:2.6.2")
     api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
     api("androidx.annotation:annotation-experimental:1.4.1")
-    implementation("androidx.collection:collection:1.4.2")
-    implementation(libs.kotlinSerializationCore)
 
-    api(libs.kotlinStdlib)
-    androidTestImplementation(project(":lifecycle:lifecycle-runtime-testing"))
+    implementation(libs.kotlinSerializationCore)
+    implementation("androidx.collection:collection:1.4.2")
+
+    androidTestImplementation("androidx.annotation:annotation:1.8.0")
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-common:2.6.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.6.2")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.2")
     androidTestImplementation(project(":internal-testutils-navigation"))
     androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation(libs.hamcrestCore)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.kotlinTest)
+    androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testExtTruth)
-    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testMonitor)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.espressoCore)
     androidTestImplementation(libs.espressoIntents)
     androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.dexmakerMockito)
-    androidTestImplementation(libs.kotlinStdlib)
-    androidTestImplementation(libs.kotlinTest)
+    androidTestImplementation(libs.mockitoCore)
 
-    lintPublish(project(':navigation:navigation-runtime-lint'))
+    lintPublish(project(":navigation:navigation-runtime-lint"))
 }
 
 android {
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index 8b2401e..8d4640b 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -47,8 +47,11 @@
     implementation(libs.javapoet)
     implementation(libs.kotlinPoet)
 
-    testImplementation(libs.junit)
     testImplementation(libs.googleCompileTesting)
+    testImplementation(libs.guava)
+    testImplementation(libs.hamcrestCore)
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
     testImplementation(project(":room:room-compiler-processing-testing"), {
         exclude group: "androidx.room", module: "room-compiler-processing"
     })
diff --git a/navigation/navigation-safe-args-gradle-plugin/build.gradle b/navigation/navigation-safe-args-gradle-plugin/build.gradle
index 87d1ac3..33499f6 100644
--- a/navigation/navigation-safe-args-gradle-plugin/build.gradle
+++ b/navigation/navigation-safe-args-gradle-plugin/build.gradle
@@ -39,13 +39,18 @@
 }
 
 dependencies {
-    implementation("com.android.tools.build:gradle:7.3.0")
-    implementation(libs.kotlinGradlePluginz)
     api(project(":navigation:navigation-safe-args-generator"))
     api(gradleApi())
+
+    implementation("com.android.tools.build:builder-model:7.3.0")
+    implementation("com.android.tools.build:gradle:7.3.0")
+    implementation("com.android.tools.build:gradle-api:7.3.0")
+    implementation(libs.kotlinGradlePluginz)
     implementation(libs.gson)
+
     testImplementation(gradleTestKit())
     testImplementation(project(":internal-testutils-gradle-plugin"))
+    testImplementation(libs.hamcrestCore)
     testImplementation(libs.junit)
     testPlugin("com.android.tools.build:gradle:7.3.0")
     testPlugin("com.android.tools.build:aapt2:7.3.0-8691043")
diff --git a/navigation/navigation-testing/build.gradle b/navigation/navigation-testing/build.gradle
index 5c80ca7..92925b0 100644
--- a/navigation/navigation-testing/build.gradle
+++ b/navigation/navigation-testing/build.gradle
@@ -32,27 +32,34 @@
 
 dependencies {
     api(project(":navigation:navigation-runtime"))
-    api("androidx.lifecycle:lifecycle-runtime-testing:2.3.1")
-    implementation(libs.kotlinSerializationCore)
+    api("androidx.lifecycle:lifecycle-runtime-testing:2.6.2")
 
+    implementation(libs.kotlinCoroutinesCore)
+    implementation(libs.kotlinSerializationCore)
+    implementation(project(":navigation:navigation-common"))
+    implementation("androidx.core:core-ktx:1.8.0")
+    implementation("androidx.lifecycle:lifecycle-common:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2")
+
+    androidTestRuntimeOnly(libs.kotlinTestJunit)
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.6.2")
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testExtTruth)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.kotlinTestJunit)
 
-    testImplementation(libs.testCore)
-    testImplementation(libs.testExtJunit)
-    testImplementation(libs.testExtTruth)
+    testRuntimeOnly(libs.testCore)
+    testRuntimeOnly(libs.kotlinTestJunit)
+    testImplementation(libs.junit)
     testImplementation(libs.testRunner)
-    testImplementation(libs.testRules)
     testImplementation(libs.truth)
-    testImplementation(libs.kotlinTestJunit)
     testImplementation(libs.robolectric)
 }
 
diff --git a/navigation/navigation-ui-ktx/build.gradle b/navigation/navigation-ui-ktx/build.gradle
index 77f8cdf..27b2f04 100644
--- a/navigation/navigation-ui-ktx/build.gradle
+++ b/navigation/navigation-ui-ktx/build.gradle
@@ -31,9 +31,6 @@
 
 dependencies {
     api(project(":navigation:navigation-ui"))
-    api(project(":navigation:navigation-runtime-ktx")) {
-        because 'Mirror navigation-ui dependency graph for -ktx artifacts'
-    }
 }
 
 androidx {
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index ab3978b..7b15eb9 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -37,19 +37,25 @@
 }
 
 dependencies {
+    api(project(":navigation:navigation-common"))
     api(project(":navigation:navigation-runtime"))
     api("androidx.customview:customview:1.1.0")
     api("androidx.drawerlayout:drawerlayout:1.1.1")
     api("com.google.android.material:material:1.4.0")
+
+    implementation("androidx.appcompat:appcompat:1.2.0")
+    implementation("androidx.core:core-ktx:1.2.0")
+    implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
     implementation("androidx.transition:transition:1.3.0")
-    api("androidx.annotation:annotation-experimental:1.4.1")
 
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testMonitor)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
diff --git a/playground-common/gradle/wrapper/gradle-wrapper.properties b/playground-common/gradle/wrapper/gradle-wrapper.properties
index c8a852e..7859569 100644
--- a/playground-common/gradle/wrapper/gradle-wrapper.properties
+++ b/playground-common/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
-distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
index a659c89..c9a1161 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
@@ -73,7 +73,7 @@
         interfaceDeclaration.declarations
             .filterIsInstance<KSClassDeclaration>()
             .filter { it.isCompanionObject }
-            .forEach { validateCompanion(name, it) }
+            .forEach { validateCompanion(name, it, logger) }
 
         val invalidModifiers =
             interfaceDeclaration.modifiers.filterNot(validInterfaceModifiers::contains)
@@ -114,41 +114,6 @@
         )
     }
 
-    private fun validateCompanion(name: String, companion: KSClassDeclaration) {
-        val nonConstValues =
-            companion.declarations
-                .filterIsInstance<KSPropertyDeclaration>()
-                .filter { !it.modifiers.contains(Modifier.CONST) }
-                .toList()
-        if (nonConstValues.isNotEmpty()) {
-            logger.error(
-                "Error in $name: companion object cannot declare non-const values (${
-                    nonConstValues.joinToString(limit = 3) { it.simpleName.getShortName() }
-                })."
-            )
-        }
-        val methods =
-            companion.declarations
-                .filterIsInstance<KSFunctionDeclaration>()
-                .filter { it.simpleName.getFullName() != "<init>" }
-                .toList()
-        if (methods.isNotEmpty()) {
-            logger.error(
-                "Error in $name: companion object cannot declare methods (${
-                    methods.joinToString(limit = 3) { it.simpleName.getShortName() }
-                })."
-            )
-        }
-        val classes = companion.declarations.filterIsInstance<KSClassDeclaration>().toList()
-        if (classes.isNotEmpty()) {
-            logger.error(
-                "Error in $name: companion object cannot declare classes (${
-                    classes.joinToString(limit = 3) { it.simpleName.getShortName() }
-                })."
-            )
-        }
-    }
-
     private fun parseMethod(method: KSFunctionDeclaration): Method {
         val name = method.qualifiedName?.getFullName() ?: method.simpleName.getFullName()
         if (!method.isAbstract) {
@@ -201,3 +166,38 @@
         )
     }
 }
+
+internal fun validateCompanion(name: String, companionDecl: KSClassDeclaration, logger: KSPLogger) {
+    val nonConstValues =
+        companionDecl.declarations
+            .filterIsInstance<KSPropertyDeclaration>()
+            .filter { !it.modifiers.contains(Modifier.CONST) }
+            .toList()
+    if (nonConstValues.isNotEmpty()) {
+        logger.error(
+            "Error in $name: companion object cannot declare non-const values (${
+                nonConstValues.joinToString(limit = 3) { it.simpleName.getShortName() }
+            })."
+        )
+    }
+    val methods =
+        companionDecl.declarations
+            .filterIsInstance<KSFunctionDeclaration>()
+            .filter { it.simpleName.getFullName() != "<init>" }
+            .toList()
+    if (methods.isNotEmpty()) {
+        logger.error(
+            "Error in $name: companion object cannot declare methods (${
+                methods.joinToString(limit = 3) { it.simpleName.getShortName() }
+            })."
+        )
+    }
+    val classes = companionDecl.declarations.filterIsInstance<KSClassDeclaration>().toList()
+    if (classes.isNotEmpty()) {
+        logger.error(
+            "Error in $name: companion object cannot declare classes (${
+                classes.joinToString(limit = 3) { it.simpleName.getShortName() }
+            })."
+        )
+    }
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
index 6f548dc..f8ad610 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
@@ -50,7 +50,7 @@
         if (!value.isPublic()) {
             logger.error("Error in $name: annotated values should be public.")
         }
-        ensureNoCompanion(value, name)
+        validateCompanions(value, name)
         ensureNoObject(value, name)
         ensureNoTypeParameters(value, name)
         ensureNoSuperTypes(value, name)
@@ -77,14 +77,11 @@
         )
     }
 
-    private fun ensureNoCompanion(classDeclaration: KSClassDeclaration, name: String) {
-        if (
-            classDeclaration.declarations
-                .filterIsInstance<KSClassDeclaration>()
-                .any(KSClassDeclaration::isCompanionObject)
-        ) {
-            logger.error("Error in $name: annotated values cannot declare companion objects.")
-        }
+    private fun validateCompanions(classDeclaration: KSClassDeclaration, name: String) {
+        classDeclaration.declarations
+            .filterIsInstance<KSClassDeclaration>()
+            .filter(KSClassDeclaration::isCompanionObject)
+            .forEach { validateCompanion(name, it, logger) }
     }
 
     private fun ensureNoObject(classDeclaration: KSClassDeclaration, name: String) {
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
index 88949a2..9b8637b 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
@@ -138,8 +138,8 @@
             .isEqualTo(
                 setOf(
                     AnnotatedEnumClass(
-                        Type(packageName = "com.mysdk", simpleName = "MyEnum"),
-                        listOf("FOO", "BAR")
+                        type = Type(packageName = "com.mysdk", simpleName = "MyEnum"),
+                        variants = listOf("FOO", "BAR")
                     )
                 )
             )
@@ -198,13 +198,15 @@
     }
 
     @Test
-    fun dataClassWithCompanionObject_fails() {
+    fun dataClassWithCompanionNonConstDeclarations_fails() {
         val dataClass =
             annotatedValue(
                 """
             |data class MySdkRequest(val id: Int) {
             |   companion object {
-            |       val someConstant = 12
+            |       const val OKAY_CONST = true && false
+            |       val someVar = 12
+            |       fun getNull() { return null }
             |   }
             |}
         """
@@ -212,8 +214,10 @@
             )
         checkSourceFails(dataClass)
             .containsExactlyErrors(
-                "Error in com.mysdk.MySdkRequest: annotated values cannot declare companion " +
-                    "objects."
+                "Error in com.mysdk.MySdkRequest: companion object cannot declare non-const " +
+                    "values (someVar).",
+                "Error in com.mysdk.MySdkRequest: companion object cannot declare methods " +
+                    "(getNull).",
             )
     }
 
diff --git a/privacysandbox/tools/tools-apigenerator/build.gradle b/privacysandbox/tools/tools-apigenerator/build.gradle
index 22d2387..ad8456b 100644
--- a/privacysandbox/tools/tools-apigenerator/build.gradle
+++ b/privacysandbox/tools/tools-apigenerator/build.gradle
@@ -58,11 +58,11 @@
 shadowJar {
     archiveClassifier = ""
     configurations = [project.configurations.shadowed]
-    relocate("kotlinx.metadata", "androidx.privacysandbox.tools.kotlinx.metadata")
-    mergeServiceFiles() // kotlinx-metadata-jvm has a service descriptor that needs transformation
-    // Exclude Kotlin metadata files from kotlinx-metadata-jvm
-    exclude 'META-INF/kotlinx-metadata-jvm.kotlin_module'
-    exclude 'META-INF/kotlinx-metadata.kotlin_module'
+    relocate("kotlin.metadata", "androidx.privacysandbox.tools.kotlin.metadata")
+    mergeServiceFiles() // kotlin-metadata-jvm has a service descriptor that needs transformation
+    // Exclude Kotlin metadata files from kotlin-metadata-jvm
+    exclude 'META-INF/kotlin-metadata-jvm.kotlin_module'
+    exclude 'META-INF/kotlin-metadata.kotlin_module'
     exclude 'META-INF/metadata.jvm.kotlin_module'
     exclude 'META-INF/metadata.kotlin_module'
 }
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
index 3379c80..c9a8534 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
@@ -18,17 +18,15 @@
 
 import androidx.privacysandbox.tools.core.generator.addCommonSettings
 import androidx.privacysandbox.tools.core.generator.build
+import androidx.privacysandbox.tools.core.generator.generateConstant
 import androidx.privacysandbox.tools.core.generator.poetClassName
 import androidx.privacysandbox.tools.core.generator.poetSpec
 import androidx.privacysandbox.tools.core.generator.poetTypeName
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
-import androidx.privacysandbox.tools.core.model.Constant
 import androidx.privacysandbox.tools.core.model.Method
-import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeSpec
 
 internal class InterfaceFileGenerator {
@@ -52,31 +50,6 @@
             .build { addCommonSettings() }
     }
 
-    private fun generateConstant(constant: Constant): PropertySpec {
-        var value = constant.value
-        // JVM bytecode stores boolean values as 0 or 1, so we convert this back to Boolean.
-        if (constant.type.simpleName == "Boolean") {
-            value = value != 0
-        }
-        if (constant.type.simpleName == "String") {
-            // Escape strings using KotlinPoet
-            value = CodeBlock.of("%S", value)
-        }
-        // JVM bytecode stores char values as a u16, so we convert this back to Char.
-        if (constant.type.simpleName == "Char") {
-            val char = (value as Int).toChar()
-            // Use KotlinPoet to handle most escape sequences, but we need to handle single-quote
-            // ourselves since Poet thinks this is a String.
-            val escapedAsString = CodeBlock.of("%S", char).toString().replace("'", "\\'")
-            val escapedChar = escapedAsString.substring(1, escapedAsString.length - 1)
-            value = "'$escapedChar'"
-        }
-
-        return PropertySpec.builder(constant.name, constant.type.poetTypeName(), KModifier.CONST)
-            .initializer("%L", value)
-            .build()
-    }
-
     private fun generateInterfaceMethod(method: Method) =
         FunSpec.builder(method.name).build {
             addModifiers(KModifier.ABSTRACT)
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
index c971df0..ff90e75 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/AnnotatedClassReader.kt
@@ -24,9 +24,9 @@
 import androidx.privacysandbox.tools.core.model.Constant
 import androidx.privacysandbox.tools.core.model.Types
 import java.nio.file.Path
-import kotlinx.metadata.KmClass
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.Metadata
+import kotlin.metadata.KmClass
+import kotlin.metadata.jvm.KotlinClassMetadata
+import kotlin.metadata.jvm.Metadata
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.Opcodes
 import org.objectweb.asm.Type
@@ -42,62 +42,51 @@
 
 data class ClassAndConstants(
     val kClass: KmClass,
-    val constants: List<Constant>?,
+    val constants: List<Constant>,
 )
 
 internal object AnnotatedClassReader {
     val annotations = listOf(PrivacySandboxService::class)
 
     fun readAnnotatedClasses(stubClassPath: Path): AnnotatedClasses {
-        val companionPaths = mutableMapOf<String, String>()
-        val classNodeByPath = mutableMapOf<String, ClassNode>()
-        for (classFile in
-            stubClassPath.toFile().walk().filter { it.isFile && it.extension == "class" }) {
-            val classNode = toClassNode(classFile.readBytes())
-            classNodeByPath[classNode.name] = classNode
-            if (
-                !(classNode.isAnnotatedWith<PrivacySandboxService>() ||
-                    classNode.isAnnotatedWith<PrivacySandboxValue>() ||
-                    classNode.isAnnotatedWith<PrivacySandboxInterface>() ||
-                    classNode.isAnnotatedWith<PrivacySandboxCallback>())
-            ) {
-                continue
-            }
-            val kotlinMetadata = parseKotlinMetadata(classNode)
-            if (kotlinMetadata.companionObject != null) {
-                val companionName = kotlinMetadata.companionObject!!
-                companionPaths[kotlinMetadata.name] = "${kotlinMetadata.name}$${companionName}"
-            }
-        }
-
         val services = mutableSetOf<ClassAndConstants>()
         val values = mutableSetOf<ClassAndConstants>()
         val callbacks = mutableSetOf<ClassAndConstants>()
         val interfaces = mutableSetOf<ClassAndConstants>()
-        classNodeByPath.values.forEach { classNode ->
-            val companionNode = companionPaths[classNode.name]?.let { classNodeByPath[it] }
-            val constants =
-                companionNode
-                    ?.let { companion ->
-                        companion.fields
-                            .filter { it.name != "\$\$INSTANCE" }
-                            .map { Constant(it.name, getConstType(it.desc), it.value) }
-                    }
-                    ?.toList()
-            if (classNode.isAnnotatedWith<PrivacySandboxService>()) {
-                services.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
+
+        stubClassPath
+            .toFile()
+            .walk()
+            .filter { it.isFile && it.extension == "class" }
+            .map { toClassNode(it.readBytes()) }
+            .forEach { classNode ->
+                // Data classes and enum classes store their constants on the object itself, rather
+                // than in the companion's class file, so we extract the constants from amongst the
+                // other fields on the annotated value/interface.
+                // Thankfully, data class fields are always non-static, and enum variants are always
+                // of the enum's type (hence not primitive or string, which consts must be).
+                // The const-allowed-types check also filters out the Companion and the VALUES
+                // array.
+                val constants =
+                    classNode.fields
+                        .filter { it.access.hasFlag(PUBLIC_STATIC_FINAL_ACCESS) }
+                        .filter { it.desc in constAllowedTypes.keys }
+                        .map { Constant(it.name, getConstType(it.desc), it.value) }
+                        .toList()
+                if (classNode.isAnnotatedWith<PrivacySandboxService>()) {
+                    services.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
+                }
+                // TODO(b/323369085): Validate that enum variants don't have methods
+                if (classNode.isAnnotatedWith<PrivacySandboxValue>()) {
+                    values.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
+                }
+                if (classNode.isAnnotatedWith<PrivacySandboxCallback>()) {
+                    callbacks.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
+                }
+                if (classNode.isAnnotatedWith<PrivacySandboxInterface>()) {
+                    interfaces.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
+                }
             }
-            // TODO(b/323369085): Validate that enum variants don't have methods
-            if (classNode.isAnnotatedWith<PrivacySandboxValue>()) {
-                values.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
-            }
-            if (classNode.isAnnotatedWith<PrivacySandboxCallback>()) {
-                callbacks.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
-            }
-            if (classNode.isAnnotatedWith<PrivacySandboxInterface>()) {
-                interfaces.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
-            }
-        }
         return AnnotatedClasses(
             services = services.toSet(),
             values = values.toSet(),
@@ -171,26 +160,31 @@
             return attributes
         }
 
+    private val constAllowedTypes =
+        mapOf(
+            "Ljava/lang/String;" to Types.string,
+            "I" to Types.int,
+            "Z" to Types.boolean,
+            "B" to Types.byte,
+            "C" to Types.char,
+            "D" to Types.double,
+            "F" to Types.float,
+            "J" to Types.long,
+            "S" to Types.short
+        )
+
     private fun getConstType(desc: String): androidx.privacysandbox.tools.core.model.Type {
-        if (desc == "Ljava/lang/String;") {
-            return Types.string
-        } else if (desc == "I") {
-            return Types.int
-        } else if (desc == "Z") {
-            return Types.boolean
-        } else if (desc == "B") {
-            return Types.byte
-        } else if (desc == "C") {
-            return Types.char
-        } else if (desc == "D") {
-            return Types.double
-        } else if (desc == "F") {
-            return Types.float
-        } else if (desc == "J") {
-            return Types.long
-        } else if (desc == "S") {
-            return Types.short
-        }
-        throw PrivacySandboxParsingException("Unrecognised constant type: '$desc'")
+        return constAllowedTypes[desc]
+            ?: throw PrivacySandboxParsingException("Unrecognised constant type: '$desc'")
     }
 }
+
+// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5
+// TODO: Once we upgrade to Java 22 we can import these constants from
+//  java.lang.classfile
+private const val PUBLIC_STATIC_FINAL_ACCESS =
+    0x0001 or // public
+        0x0008 or // static
+        0x0010 // final
+
+private fun Int.hasFlag(flag: Int) = flag and this == flag
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
index d1e1c05..1235106 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
@@ -29,18 +29,18 @@
 import androidx.privacysandbox.tools.core.model.ValueProperty
 import androidx.privacysandbox.tools.core.validator.ModelValidator
 import java.nio.file.Path
-import kotlinx.metadata.ClassKind
-import kotlinx.metadata.ClassName
-import kotlinx.metadata.KmClass
-import kotlinx.metadata.KmClassifier
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.KmProperty
-import kotlinx.metadata.KmType
-import kotlinx.metadata.isData
-import kotlinx.metadata.isNullable
-import kotlinx.metadata.isSuspend
-import kotlinx.metadata.isVar
-import kotlinx.metadata.kind
+import kotlin.metadata.ClassKind
+import kotlin.metadata.ClassName
+import kotlin.metadata.KmClass
+import kotlin.metadata.KmClassifier
+import kotlin.metadata.KmFunction
+import kotlin.metadata.KmProperty
+import kotlin.metadata.KmType
+import kotlin.metadata.isData
+import kotlin.metadata.isNullable
+import kotlin.metadata.isSuspend
+import kotlin.metadata.isVar
+import kotlin.metadata.kind
 
 internal object ApiStubParser {
     /**
@@ -83,7 +83,7 @@
             type = type,
             superTypes = superTypes,
             methods = service.functions.map(this::parseMethod),
-            constants = interfaceAndConstants.constants?.toList() ?: listOf(),
+            constants = interfaceAndConstants.constants,
         )
     }
 
@@ -117,9 +117,17 @@
         }
 
         return if (value.isData) {
-            AnnotatedDataClass(type, parseProperties(type, value))
+            AnnotatedDataClass(
+                type,
+                constants = classAndConstants.constants,
+                properties = parseProperties(type, value)
+            )
         } else {
-            AnnotatedEnumClass(type, value.enumEntries.toList())
+            AnnotatedEnumClass(
+                type,
+                constants = classAndConstants.constants,
+                variants = value.enumEntries.toList()
+            )
         }
     }
 
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
index 6359994..1a42b78 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/input/com/sdkwithvalues/SdkInterface.kt
@@ -34,12 +34,21 @@
     val maybeNumber: Int?,
     val maybeInterface: MyInterface?,
     val maybeBundle: Bundle?,
-)
+) {
+    companion object {
+        const val DEFAULT_USER_ID = 42
+        const val DEFAULT_SEPARATOR = '"'
+    }
+}
 
 @PrivacySandboxValue
 enum class RequestFlag {
     UP,
-    DOWN,
+    DOWN;
+
+    companion object {
+        const val STEP_SIZE = 5
+    }
 }
 
 @PrivacySandboxValue
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/InnerSdkValue.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/InnerSdkValue.kt
index b88cc6a..1be6291 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/InnerSdkValue.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/InnerSdkValue.kt
@@ -17,4 +17,10 @@
     public val maybeNumber: Int?,
     public val maybeInterface: MyInterface?,
     public val maybeBundle: Bundle?,
-)
+) {
+    public companion object {
+        public const val DEFAULT_USER_ID: Int = 42
+
+        public const val DEFAULT_SEPARATOR: Char = '\"'
+    }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt
index 9a969f5..4f90ade 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlag.kt
@@ -3,4 +3,9 @@
 public enum class RequestFlag {
     UP,
     DOWN,
+    ;
+
+    public companion object {
+        public const val STEP_SIZE: Int = 5
+    }
 }
diff --git a/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/ClassReader.kt b/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/ClassReader.kt
index f83ecd0..aaef95c 100644
--- a/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/ClassReader.kt
+++ b/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/ClassReader.kt
@@ -17,9 +17,9 @@
 package androidx.privacysandbox.tools.apipackager
 
 import androidx.privacysandbox.tools.core.PrivacySandboxParsingException
-import kotlinx.metadata.KmClass
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.Metadata
+import kotlin.metadata.KmClass
+import kotlin.metadata.jvm.KotlinClassMetadata
+import kotlin.metadata.jvm.Metadata
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.Opcodes
 import org.objectweb.asm.Type
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
index 523b668b..ccb01d9 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueFileGenerator.kt
@@ -19,6 +19,8 @@
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
 import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
+import androidx.privacysandbox.tools.core.model.Constant
+import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.PropertySpec
@@ -44,10 +46,49 @@
                                 .build()
                         }
                     )
+                    if (value.constants.isNotEmpty()) {
+                        addType(
+                            TypeSpec.companionObjectBuilder().build {
+                                addProperties(value.constants.map(::generateConstant))
+                            }
+                        )
+                    }
                 }
             is AnnotatedEnumClass ->
                 TypeSpec.enumBuilder(value.type.poetClassName()).build {
                     value.variants.forEach(::addEnumConstant)
+                    if (value.constants.isNotEmpty()) {
+                        addType(
+                            TypeSpec.companionObjectBuilder().build {
+                                addProperties(value.constants.map(::generateConstant))
+                            }
+                        )
+                    }
                 }
         }
 }
+
+fun generateConstant(constant: Constant): PropertySpec {
+    var value = constant.value
+    // JVM bytecode stores boolean values as 0 or 1, so we convert this back to Boolean.
+    if (constant.type.simpleName == "Boolean") {
+        value = value != 0
+    }
+    if (constant.type.simpleName == "String") {
+        // Escape strings using KotlinPoet
+        value = CodeBlock.of("%S", value)
+    }
+    // JVM bytecode stores char values as a u16, so we convert this back to Char.
+    if (constant.type.simpleName == "Char") {
+        val char = (value as Int).toChar()
+        // Use KotlinPoet to handle most escape sequences, but we need to handle single-quote
+        // ourselves since Poet thinks this is a String.
+        val escapedAsString = CodeBlock.of("%S", char).toString().replace("'", "\\'")
+        val escapedChar = escapedAsString.substring(1, escapedAsString.length - 1)
+        value = "'$escapedChar'"
+    }
+
+    return PropertySpec.builder(constant.name, constant.type.poetTypeName(), KModifier.CONST)
+        .initializer("%L", value)
+        .build()
+}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/AnnotatedValue.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/AnnotatedValue.kt
index 86a7f18..91d49d6 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/AnnotatedValue.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/AnnotatedValue.kt
@@ -16,14 +16,19 @@
 
 package androidx.privacysandbox.tools.core.model
 
-sealed class AnnotatedValue(open val type: Type)
+sealed class AnnotatedValue(
+    open val type: Type,
+    open val constants: List<Constant> = emptyList(),
+)
 
 data class AnnotatedDataClass(
     override val type: Type,
+    override val constants: List<Constant> = emptyList(),
     val properties: List<ValueProperty>,
 ) : AnnotatedValue(type)
 
 data class AnnotatedEnumClass(
     override val type: Type,
+    override val constants: List<Constant> = emptyList(),
     val variants: List<String>,
 ) : AnnotatedValue(type)
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
index 6aeff74..d7db134 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlValueGeneratorTest.kt
@@ -39,31 +39,33 @@
     fun generate() {
         val innerEnum =
             AnnotatedEnumClass(
-                Type(packageName = "com.mysdk", simpleName = "InnerEnum"),
-                listOf("ONE, TWO, THREE")
+                type = Type(packageName = "com.mysdk", simpleName = "InnerEnum"),
+                variants = listOf("ONE, TWO, THREE")
             )
         val innerValue =
             AnnotatedDataClass(
-                Type(packageName = "com.mysdk", simpleName = "InnerValue"),
-                listOf(
-                    ValueProperty("intProperty", Types.int),
-                    ValueProperty("booleanProperty", Types.boolean),
-                    ValueProperty("longProperty", Types.long),
-                    ValueProperty("maybeFloatProperty", Types.float.asNullable()),
-                    ValueProperty("maybeByteProperty", Types.byte.asNullable()),
-                    ValueProperty("enumProperty", innerEnum.type),
-                    ValueProperty("bundleProperty", Types.bundle),
-                    ValueProperty("maybeBundleProperty", Types.bundle.asNullable())
-                )
+                type = Type(packageName = "com.mysdk", simpleName = "InnerValue"),
+                properties =
+                    listOf(
+                        ValueProperty("intProperty", Types.int),
+                        ValueProperty("booleanProperty", Types.boolean),
+                        ValueProperty("longProperty", Types.long),
+                        ValueProperty("maybeFloatProperty", Types.float.asNullable()),
+                        ValueProperty("maybeByteProperty", Types.byte.asNullable()),
+                        ValueProperty("enumProperty", innerEnum.type),
+                        ValueProperty("bundleProperty", Types.bundle),
+                        ValueProperty("maybeBundleProperty", Types.bundle.asNullable())
+                    )
             )
         val outerValue =
             AnnotatedDataClass(
-                Type(packageName = "com.mysdk", simpleName = "OuterValue"),
-                listOf(
-                    ValueProperty("innerValue", innerValue.type),
-                    ValueProperty("innerValueList", Types.list(innerValue.type)),
-                    ValueProperty("maybeInnerValue", innerValue.type.asNullable()),
-                )
+                type = Type(packageName = "com.mysdk", simpleName = "OuterValue"),
+                properties =
+                    listOf(
+                        ValueProperty("innerValue", innerValue.type),
+                        ValueProperty("innerValueList", Types.list(innerValue.type)),
+                        ValueProperty("maybeInnerValue", innerValue.type.asNullable()),
+                    )
             )
 
         val api =
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
index 1d05403..0ede714 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
@@ -256,14 +256,6 @@
             canvas.drawColor(viewColor)
             canvas.drawText(text, 75F, 75F, paint)
             pointerIdToPathMap.forEach { (_, path) -> canvas.drawPath(path, paint) }
-
-            setOnClickListener {
-                Log.i(TAG, "Click on ad detected")
-                val visitUrl = Intent(Intent.ACTION_VIEW)
-                visitUrl.data = Uri.parse(GOOGLE_URL)
-                visitUrl.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                context.startActivity(visitUrl)
-            }
         }
     }
 
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_resize.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_resize.xml
index d4acda9..04451fa 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_resize.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_resize.xml
@@ -30,7 +30,7 @@
         android:layout_marginEnd="16dp"
         android:layout_marginStart="16dp"
         android:layout_marginTop="16dp"
-        android:background="#FF0000" />
+        android:background="#FFFFFF" />
 
     <LinearLayout
         android:layout_width="wrap_content"
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_scroll.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_scroll.xml
index e391ec4..8bdb2e8 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_scroll.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/fragment_scroll.xml
@@ -39,7 +39,7 @@
                 android:layout_width="wrap_content"
                 android:layout_weight="2"
                 android:layout_height="0dp"
-                android:background="#FF0000" />
+                android:background="#FFFFFF" />
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 1621feb..d9dafed 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -101,7 +101,7 @@
             clientExecutor: Executor,
             client: SandboxedUiAdapter.SessionClient
         ) {
-            client.onSessionError(Exception("Error in openSession()"))
+            clientExecutor.execute { client.onSessionError(Exception("Error in openSession()")) }
         }
     }
 
@@ -133,13 +133,15 @@
         ) {
             internalClient = client
             testSession = TestSession(context, initialWidth, initialHeight, signalOptions)
-            if (!delayOpenSessionCallback) {
-                client.onSessionOpened(testSession!!)
+            clientExecutor.execute {
+                if (!delayOpenSessionCallback) {
+                    client.onSessionOpened(testSession!!)
+                }
+                isSessionOpened = true
+                this.isZOrderOnTop = isZOrderOnTop
+                this.inputToken = windowInputToken
+                openSessionLatch.countDown()
             }
-            isSessionOpened = true
-            this.isZOrderOnTop = isZOrderOnTop
-            this.inputToken = windowInputToken
-            openSessionLatch.countDown()
         }
 
         internal fun sendOnSessionOpened() {
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/PoolingContainerTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/PoolingContainerTests.kt
index ffa54e7..3d6f679 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/PoolingContainerTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/PoolingContainerTests.kt
@@ -110,9 +110,6 @@
 
     @Test
     fun testPoolingContainerListener_AllViewsRemovedFromContainer() {
-        // TODO(b/309848703): Stop skipping this for backwards compat flow
-        assumeTrue(!invokeBackwardsCompatFlow)
-
         val adapter =
             createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(isNestedView = false)
 
@@ -124,9 +121,6 @@
 
     @Test
     fun testPoolingContainerListener_ContainerRemovedFromLayout() {
-        // TODO(b/309848703): Stop skipping this for backwards compat flow
-        assumeTrue(!invokeBackwardsCompatFlow)
-
         val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(isNestedView = true)
 
         activityScenarioRule.withActivity { linearLayout.removeView(recyclerView) }
@@ -136,9 +130,6 @@
 
     @Test
     fun testPoolingContainerListener_ViewWithinAnotherView_AllViewsRemovedFromContainer() {
-        // TODO(b/309848703): Stop skipping this for backwards compat flow
-        assumeTrue(!invokeBackwardsCompatFlow)
-
         val adapter =
             createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(isNestedView = false)
 
@@ -150,9 +141,6 @@
 
     @Test
     fun testPoolingContainerListener_ViewWithinAnotherView_ContainerRemovedFromLayout() {
-        // TODO(b/309848703): Stop skipping this for backwards compat flow
-        assumeTrue(!invokeBackwardsCompatFlow)
-
         val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(isNestedView = true)
 
         activityScenarioRule.withActivity { linearLayout.removeView(recyclerView) }
@@ -165,8 +153,6 @@
     fun testPoolingContainerListener_NotifyFetchUiForSession() {
         // verifyColorOfScreenshot is only available for U+ devices.
         assumeTrue(SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-        // TODO(b/309848703): Stop skipping this for backwards compat flow
-        assumeTrue(!invokeBackwardsCompatFlow)
 
         val recyclerViewAdapter = RecyclerViewTestAdapterForFetchingUi()
 
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
index 85868f9..6b52951 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
@@ -160,12 +160,14 @@
             this.initialWidth = initialWidth
             session =
                 if (hasFailingTestSession) {
-                    FailingTestSession(context, client)
+                    FailingTestSession(context, client, clientExecutor)
                 } else {
                     TestSession(context, client, placeViewInsideFrameLayout)
                 }
-            client.onSessionOpened(session)
-            openSessionLatch.countDown()
+            clientExecutor.execute {
+                client.onSessionOpened(session)
+                openSessionLatch.countDown()
+            }
         }
 
         /**
@@ -173,11 +175,14 @@
          */
         inner class FailingTestSession(
             private val context: Context,
-            sessionClient: SandboxedUiAdapter.SessionClient
+            sessionClient: SandboxedUiAdapter.SessionClient,
+            private val clientExecutor: Executor,
         ) : TestSession(context, sessionClient) {
             override val view: View
                 get() {
-                    sessionClient.onSessionError(Throwable("Test Session Exception"))
+                    clientExecutor.execute {
+                        sessionClient.onSessionError(Throwable("Test Session Exception"))
+                    }
                     return View(context)
                 }
 
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
new file mode 100644
index 0000000..fb45166
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.room.Room
+import androidx.sqlite.SQLiteDriver
+import androidx.sqlite.driver.AndroidSQLiteDriver
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlinx.coroutines.Dispatchers
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class PagingTest(private val driver: SQLiteDriver) : BasePagingTest() {
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val file = instrumentation.targetContext.getDatabasePath("test.db")
+
+    override fun getRoomDatabase(): SampleDatabase {
+        return Room.databaseBuilder<SampleDatabase>(
+                context = instrumentation.targetContext,
+                name = file.path
+            )
+            .setDriver(driver)
+            .setQueryCoroutineContext(Dispatchers.IO)
+            .build()
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "driver={0}")
+        fun drivers() = arrayOf(BundledSQLiteDriver(), AndroidSQLiteDriver())
+    }
+
+    @BeforeTest
+    fun before() {
+        assertThat(file).isNotNull()
+        file.parentFile?.mkdirs()
+        deleteDatabaseFile()
+    }
+
+    @AfterTest
+    fun after() {
+        deleteDatabaseFile()
+    }
+
+    private fun deleteDatabaseFile() {
+        instrumentation.targetContext.deleteDatabase(file.name)
+    }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BasePagingTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BasePagingTest.kt
new file mode 100644
index 0000000..0141bb2
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BasePagingTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadResult
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+abstract class BasePagingTest {
+
+    abstract fun getRoomDatabase(): SampleDatabase
+
+    @Test
+    fun pagingQuery() = runTest {
+        val db = getRoomDatabase()
+        val entity1 = SampleEntity(1, 1)
+        val entity2 = SampleEntity(2, 2)
+        val sampleEntities = listOf(entity1, entity2)
+        val dao = db.dao()
+
+        dao.insertSampleEntityList(sampleEntities)
+        val pagingSource = dao.getAllIds()
+
+        val onlyLoadFirst =
+            pagingSource.load(
+                PagingSource.LoadParams.Refresh(
+                    key = null,
+                    loadSize = 1,
+                    placeholdersEnabled = true
+                )
+            ) as LoadResult.Page
+        assertThat(onlyLoadFirst.data).containsExactly(entity1)
+
+        val loadAll =
+            pagingSource.load(
+                PagingSource.LoadParams.Refresh(
+                    key = null,
+                    loadSize = 2,
+                    placeholdersEnabled = true
+                )
+            ) as LoadResult.Page
+        assertThat(loadAll.data).containsExactlyElementsIn(sampleEntities)
+    }
+
+    @Test
+    fun pagingQueryWithParams() = runTest {
+        val db = getRoomDatabase()
+        val entity1 = SampleEntity(1, 1)
+        val entity2 = SampleEntity(2, 2)
+        val entity3 = SampleEntity(3, 3)
+        val sampleEntities = listOf(entity1, entity2, entity3)
+        val dao = db.dao()
+
+        dao.insertSampleEntityList(sampleEntities)
+        val pagingSource = dao.getAllIdsWithArgs(1)
+
+        val onlyLoadFirst =
+            pagingSource.load(
+                PagingSource.LoadParams.Refresh(
+                    key = null,
+                    loadSize = 1,
+                    placeholdersEnabled = true
+                )
+            ) as LoadResult.Page
+        assertThat(onlyLoadFirst.data).containsExactly(entity2)
+
+        val loadAll =
+            pagingSource.load(
+                PagingSource.LoadParams.Refresh(
+                    key = null,
+                    loadSize = 2,
+                    placeholdersEnabled = true
+                )
+            ) as LoadResult.Page
+        assertThat(loadAll.data).containsExactlyElementsIn(listOf(entity2, entity3))
+    }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
index 760c416..78e59dc 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
@@ -18,8 +18,6 @@
 
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
-import androidx.paging.PagingSource
-import androidx.paging.PagingSource.LoadResult
 import androidx.room.RoomRawQuery
 import androidx.room.execSQL
 import androidx.room.immediateTransaction
@@ -505,67 +503,4 @@
             .hasMessageThat()
             .contains("Only bind*() calls are allowed")
     }
-
-    @Test
-    fun simplePagingQuery() = runTest {
-        val entity1 = SampleEntity(1, 1)
-        val entity2 = SampleEntity(2, 2)
-        val sampleEntities = listOf(entity1, entity2)
-        val dao = db.dao()
-
-        dao.insertSampleEntityList(sampleEntities)
-        val pagingSource = dao.getAllIds()
-
-        val onlyLoadFirst =
-            pagingSource.load(
-                PagingSource.LoadParams.Refresh(
-                    key = null,
-                    loadSize = 1,
-                    placeholdersEnabled = true
-                )
-            ) as LoadResult.Page
-        assertThat(onlyLoadFirst.data).containsExactly(entity1)
-
-        val loadAll =
-            pagingSource.load(
-                PagingSource.LoadParams.Refresh(
-                    key = null,
-                    loadSize = 2,
-                    placeholdersEnabled = true
-                )
-            ) as LoadResult.Page
-        assertThat(loadAll.data).containsExactlyElementsIn(sampleEntities)
-    }
-
-    @Test
-    fun pagingQueryWithParams() = runTest {
-        val entity1 = SampleEntity(1, 1)
-        val entity2 = SampleEntity(2, 2)
-        val entity3 = SampleEntity(3, 3)
-        val sampleEntities = listOf(entity1, entity2, entity3)
-        val dao = db.dao()
-
-        dao.insertSampleEntityList(sampleEntities)
-        val pagingSource = dao.getAllIdsWithArgs(1)
-
-        val onlyLoadFirst =
-            pagingSource.load(
-                PagingSource.LoadParams.Refresh(
-                    key = null,
-                    loadSize = 1,
-                    placeholdersEnabled = true
-                )
-            ) as LoadResult.Page
-        assertThat(onlyLoadFirst.data).containsExactly(entity2)
-
-        val loadAll =
-            pagingSource.load(
-                PagingSource.LoadParams.Refresh(
-                    key = null,
-                    loadSize = 2,
-                    placeholdersEnabled = true
-                )
-            ) as LoadResult.Page
-        assertThat(loadAll.data).containsExactlyElementsIn(listOf(entity2, entity3))
-    }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
new file mode 100644
index 0000000..7c9901c
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.io.path.createTempFile
+import kotlinx.coroutines.Dispatchers
+
+class PagingTest : BasePagingTest() {
+
+    override fun getRoomDatabase(): SampleDatabase {
+        val tempFile = createTempFile("test.db").also { it.toFile().deleteOnExit() }
+        return Room.databaseBuilder<SampleDatabase>(name = tempFile.toString())
+            .setDriver(BundledSQLiteDriver())
+            .setQueryCoroutineContext(Dispatchers.IO)
+            .build()
+    }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
new file mode 100644
index 0000000..67c3738
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/PagingTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.random.Random
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.IO
+import platform.posix.remove
+
+class PagingTest : BasePagingTest() {
+
+    private val filename = "/tmp/test-${Random.nextInt()}.db"
+
+    override fun getRoomDatabase(): SampleDatabase {
+        return Room.databaseBuilder<SampleDatabase>(name = filename)
+            .setDriver(BundledSQLiteDriver())
+            .setQueryCoroutineContext(Dispatchers.IO)
+            .build()
+    }
+
+    @BeforeTest
+    fun before() {
+        deleteDatabaseFile()
+    }
+
+    @AfterTest
+    fun after() {
+        deleteDatabaseFile()
+    }
+
+    private fun deleteDatabaseFile() {
+        remove(filename)
+        remove("$filename-wal")
+        remove("$filename-shm")
+    }
+}
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 8c6ea4f..6e083ee 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -45,11 +45,11 @@
 shadowJar {
     archiveClassifier = ""
     configurations = [project.configurations.shadowed]
-    relocate("kotlinx.metadata", "androidx.room.jarjarred.kotlinx.metadata")
-    mergeServiceFiles() // kotlinx-metadata-jvm has a service descriptor that needs transformation
-    // Exclude Kotlin metadata files from kotlinx-metadata-jvm
-    exclude 'META-INF/kotlinx-metadata-jvm.kotlin_module'
-    exclude 'META-INF/kotlinx-metadata.kotlin_module'
+    relocate("kotlin.metadata", "androidx.room.jarjarred.kotlin.metadata")
+    mergeServiceFiles() // kotlin-metadata-jvm has a service descriptor that needs transformation
+    // Exclude Kotlin metadata files from kotlin-metadata-jvm
+    exclude 'META-INF/kotlin-metadata-jvm.kotlin_module'
+    exclude 'META-INF/kotlin-metadata.kotlin_module'
     exclude 'META-INF/metadata.jvm.kotlin_module'
     exclude 'META-INF/metadata.kotlin_module'
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
index 73f99d5..2f96281 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -29,36 +29,36 @@
 import javax.lang.model.element.ExecutableElement
 import javax.lang.model.element.VariableElement
 import javax.tools.Diagnostic
-import kotlinx.metadata.ClassKind
-import kotlinx.metadata.KmAnnotation
-import kotlinx.metadata.KmAnnotationArgument
-import kotlinx.metadata.KmClass
-import kotlinx.metadata.KmClassifier
-import kotlinx.metadata.KmConstructor
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.KmProperty
-import kotlinx.metadata.KmType
-import kotlinx.metadata.KmTypeParameter
-import kotlinx.metadata.KmValueParameter
-import kotlinx.metadata.Visibility
-import kotlinx.metadata.declaresDefaultValue
-import kotlinx.metadata.isData
-import kotlinx.metadata.isDelegated
-import kotlinx.metadata.isExpect
-import kotlinx.metadata.isFunInterface
-import kotlinx.metadata.isNullable
-import kotlinx.metadata.isSecondary
-import kotlinx.metadata.isSuspend
-import kotlinx.metadata.isValue
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.annotations
-import kotlinx.metadata.jvm.fieldSignature
-import kotlinx.metadata.jvm.getterSignature
-import kotlinx.metadata.jvm.setterSignature
-import kotlinx.metadata.jvm.signature
-import kotlinx.metadata.jvm.syntheticMethodForAnnotations
-import kotlinx.metadata.kind
-import kotlinx.metadata.visibility
+import kotlin.metadata.ClassKind
+import kotlin.metadata.KmAnnotation
+import kotlin.metadata.KmAnnotationArgument
+import kotlin.metadata.KmClass
+import kotlin.metadata.KmClassifier
+import kotlin.metadata.KmConstructor
+import kotlin.metadata.KmFunction
+import kotlin.metadata.KmProperty
+import kotlin.metadata.KmType
+import kotlin.metadata.KmTypeParameter
+import kotlin.metadata.KmValueParameter
+import kotlin.metadata.Visibility
+import kotlin.metadata.declaresDefaultValue
+import kotlin.metadata.isData
+import kotlin.metadata.isDelegated
+import kotlin.metadata.isExpect
+import kotlin.metadata.isFunInterface
+import kotlin.metadata.isNullable
+import kotlin.metadata.isSecondary
+import kotlin.metadata.isSuspend
+import kotlin.metadata.isValue
+import kotlin.metadata.jvm.KotlinClassMetadata
+import kotlin.metadata.jvm.annotations
+import kotlin.metadata.jvm.fieldSignature
+import kotlin.metadata.jvm.getterSignature
+import kotlin.metadata.jvm.setterSignature
+import kotlin.metadata.jvm.signature
+import kotlin.metadata.jvm.syntheticMethodForAnnotations
+import kotlin.metadata.kind
+import kotlin.metadata.visibility
 
 internal interface KmData
 
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
index 0ea2bc0..7a043aa 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
@@ -22,7 +22,7 @@
 import androidx.paging.PagingSource.LoadResult
 import androidx.room.RoomDatabase
 import androidx.room.RoomRawQuery
-import androidx.room.immediateTransaction
+import androidx.room.Transactor.SQLiteTransactionType
 import androidx.room.paging.util.INITIAL_ITEM_COUNT
 import androidx.room.paging.util.queryDatabase
 import androidx.room.paging.util.queryItemCount
@@ -110,7 +110,7 @@
      */
     private suspend fun initialLoad(params: LoadParams<Int>): LoadResult<Int, Value> {
         return db.useReaderConnection { connection ->
-            connection.immediateTransaction {
+            connection.withTransaction(SQLiteTransactionType.DEFERRED) {
                 val tempCount = queryItemCount(sourceQuery, db)
                 itemCount.value = tempCount
                 queryDatabase(
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 260e53c..c8434e1 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -293,11 +293,19 @@
 
   public static final class RevealValue.Companion {
     method public int getCovered();
-    method public int getRevealed();
-    method public int getRevealing();
+    method public int getLeftRevealed();
+    method public int getLeftRevealing();
+    method @Deprecated public int getRevealed();
+    method @Deprecated public int getRevealing();
+    method public int getRightRevealed();
+    method public int getRightRevealing();
     property public final int Covered;
-    property public final int Revealed;
-    property public final int Revealing;
+    property public final int LeftRevealed;
+    property public final int LeftRevealing;
+    property @Deprecated public final int Revealed;
+    property @Deprecated public final int Revealing;
+    property public final int RightRevealed;
+    property public final int RightRevealing;
   }
 
   public interface ScrollInfoProvider {
@@ -320,6 +328,19 @@
     method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState state);
   }
 
+  @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi @kotlin.jvm.JvmInline public final value class SwipeDirection {
+    method public int getValue();
+    property public final int value;
+    field public static final androidx.wear.compose.foundation.SwipeDirection.Companion Companion;
+  }
+
+  public static final class SwipeDirection.Companion {
+    method public int getBoth();
+    method public int getRightToLeft();
+    property public final int Both;
+    property public final int RightToLeft;
+  }
+
   public final class SwipeToDismissBoxDefaults {
     method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
     method public float getEdgeWidth();
@@ -352,7 +373,7 @@
 
   public final class SwipeToRevealKt {
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static void SwipeToReveal(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.wear.compose.foundation.RevealState state, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoAction, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> createAnchors(optional float coveredAnchor, optional float revealingAnchor, optional float revealedAnchor);
+    method @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> createAnchors(optional float coveredAnchor, optional float revealingAnchor, optional float revealedAnchor, optional int swipeDirection);
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold, optional java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> anchors);
   }
 
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 260e53c..c8434e1 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -293,11 +293,19 @@
 
   public static final class RevealValue.Companion {
     method public int getCovered();
-    method public int getRevealed();
-    method public int getRevealing();
+    method public int getLeftRevealed();
+    method public int getLeftRevealing();
+    method @Deprecated public int getRevealed();
+    method @Deprecated public int getRevealing();
+    method public int getRightRevealed();
+    method public int getRightRevealing();
     property public final int Covered;
-    property public final int Revealed;
-    property public final int Revealing;
+    property public final int LeftRevealed;
+    property public final int LeftRevealing;
+    property @Deprecated public final int Revealed;
+    property @Deprecated public final int Revealing;
+    property public final int RightRevealed;
+    property public final int RightRevealing;
   }
 
   public interface ScrollInfoProvider {
@@ -320,6 +328,19 @@
     method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState state);
   }
 
+  @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi @kotlin.jvm.JvmInline public final value class SwipeDirection {
+    method public int getValue();
+    property public final int value;
+    field public static final androidx.wear.compose.foundation.SwipeDirection.Companion Companion;
+  }
+
+  public static final class SwipeDirection.Companion {
+    method public int getBoth();
+    method public int getRightToLeft();
+    property public final int Both;
+    property public final int RightToLeft;
+  }
+
   public final class SwipeToDismissBoxDefaults {
     method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
     method public float getEdgeWidth();
@@ -352,7 +373,7 @@
 
   public final class SwipeToRevealKt {
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static void SwipeToReveal(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.wear.compose.foundation.RevealState state, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoAction, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> createAnchors(optional float coveredAnchor, optional float revealingAnchor, optional float revealedAnchor);
+    method @SuppressCompatibility @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> createAnchors(optional float coveredAnchor, optional float revealingAnchor, optional float revealedAnchor, optional int swipeDirection);
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.foundation.ExperimentalWearFoundationApi public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold, optional java.util.Map<androidx.wear.compose.foundation.RevealValue,java.lang.Float> anchors);
   }
 
diff --git a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/SwipeToRevealSample.kt b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/SwipeToRevealSample.kt
index 008a0b0..4ac4377 100644
--- a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/SwipeToRevealSample.kt
+++ b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/SwipeToRevealSample.kt
@@ -185,7 +185,7 @@
                                     listOf(
                                         CustomAccessibilityAction("Delete") {
                                             coroutineScope.launch {
-                                                revealState.animateTo(RevealValue.Revealed)
+                                                revealState.animateTo(RevealValue.RightRevealed)
                                             }
                                             true
                                         }
@@ -199,7 +199,7 @@
                                         .background(Color.Red, actionShape)
                                         .clickable {
                                             coroutineScope.launch {
-                                                revealState.animateTo(RevealValue.Revealed)
+                                                revealState.animateTo(RevealValue.RightRevealed)
                                             }
                                         },
                                 contentAlignment = Alignment.Center
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
index 340ddc8..98a92bd 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
@@ -80,7 +80,7 @@
     fun onRevealing_drawsAction() {
         rule.setContent {
             swipeToRevealWithDefaults(
-                state = rememberRevealState(initialValue = RevealValue.Revealing),
+                state = rememberRevealState(initialValue = RevealValue.RightRevealing),
                 primaryAction = { actionContent(modifier = Modifier.testTag(TEST_TAG)) }
             )
         }
@@ -115,13 +115,13 @@
 
     @Test
     fun stateToSwiped_onFullSwipeLeft() {
-        verifyGesture(revealValue = RevealValue.Revealed, gesture = { swipeLeft() })
+        verifyGesture(revealValue = RevealValue.RightRevealed, gesture = { swipeLeft() })
     }
 
     @Test
     fun stateToIconsVisible_onPartialSwipeLeft() {
         verifyGesture(
-            revealValue = RevealValue.Revealing,
+            revealValue = RevealValue.RightRevealing,
             gesture = { swipeLeft(startX = width / 2f, endX = 0f) }
         )
     }
@@ -132,7 +132,9 @@
         rule.setContent {
             revealState =
                 rememberRevealState(
-                    confirmValueChange = { revealValue -> revealValue != RevealValue.Revealing }
+                    confirmValueChange = { revealValue ->
+                        revealValue != RevealValue.RightRevealing
+                    }
                 )
             swipeToRevealWithDefaults(state = revealState, modifier = Modifier.testTag(TEST_TAG))
         }
@@ -152,7 +154,9 @@
             revealStateOne = rememberRevealState()
             revealStateTwo =
                 rememberRevealState(
-                    confirmValueChange = { revealValue -> revealValue != RevealValue.Revealing }
+                    confirmValueChange = { revealValue ->
+                        revealValue != RevealValue.RightRevealing
+                    }
                 )
             Column {
                 swipeToRevealWithDefaults(
@@ -177,7 +181,7 @@
         }
 
         rule.runOnIdle {
-            assertEquals(RevealValue.Revealing, revealStateOne.currentValue)
+            assertEquals(RevealValue.RightRevealing, revealStateOne.currentValue)
             assertEquals(RevealValue.Covered, revealStateTwo.currentValue)
         }
     }
@@ -215,7 +219,7 @@
 
         rule.runOnIdle {
             assertEquals(RevealValue.Covered, revealStateOne.currentValue)
-            assertEquals(RevealValue.Revealing, revealStateTwo.currentValue)
+            assertEquals(RevealValue.RightRevealing, revealStateTwo.currentValue)
         }
     }
 
@@ -252,8 +256,8 @@
 
         rule.runOnIdle {
             // assert that state does not reset
-            assertEquals(RevealValue.Revealed, revealStateOne.currentValue)
-            assertEquals(RevealValue.Revealing, revealStateTwo.currentValue)
+            assertEquals(RevealValue.RightRevealed, revealStateOne.currentValue)
+            assertEquals(RevealValue.RightRevealing, revealStateTwo.currentValue)
         }
     }
 
@@ -270,9 +274,9 @@
             val coroutineScope = rememberCoroutineScope()
             coroutineScope.launch {
                 // First change
-                revealStateOne.snapTo(RevealValue.Revealing)
+                revealStateOne.snapTo(RevealValue.RightRevealing)
                 // Second change, in a different state
-                revealStateTwo.snapTo(RevealValue.Revealing)
+                revealStateTwo.snapTo(RevealValue.RightRevealing)
             }
         }
 
@@ -283,7 +287,7 @@
     fun onMultiSnapOnSameState_doesNotReset() {
         lateinit var revealStateOne: RevealState
         lateinit var revealStateTwo: RevealState
-        val lastValue = RevealValue.Revealed
+        val lastValue = RevealValue.RightRevealed
         rule.setContent {
             revealStateOne = rememberRevealState()
             revealStateTwo = rememberRevealState()
@@ -292,7 +296,7 @@
 
             val coroutineScope = rememberCoroutineScope()
             coroutineScope.launch {
-                revealStateOne.snapTo(RevealValue.Revealing) // First change
+                revealStateOne.snapTo(RevealValue.RightRevealing) // First change
                 revealStateOne.snapTo(lastValue) // Second change, same state
             }
         }
@@ -304,7 +308,7 @@
     fun onSecondaryActionClick_setsLastClickAction() =
         verifyLastClickAction(
             expectedClickType = RevealActionType.SecondaryAction,
-            initialRevealValue = RevealValue.Revealing,
+            initialRevealValue = RevealValue.RightRevealing,
             secondaryActionModifier = Modifier.testTag(TEST_TAG)
         )
 
@@ -312,7 +316,7 @@
     fun onPrimaryActionClick_setsLastClickAction() =
         verifyLastClickAction(
             expectedClickType = RevealActionType.PrimaryAction,
-            initialRevealValue = RevealValue.Revealing,
+            initialRevealValue = RevealValue.RightRevealing,
             primaryActionModifier = Modifier.testTag(TEST_TAG)
         )
 
@@ -320,7 +324,7 @@
     fun onUndoActionClick_setsLastClickAction() =
         verifyLastClickAction(
             expectedClickType = RevealActionType.UndoAction,
-            initialRevealValue = RevealValue.Revealed,
+            initialRevealValue = RevealValue.RightRevealed,
             undoActionModifier = Modifier.testTag(TEST_TAG)
         )
 
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
index 26080e1..1c4a997 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
@@ -85,12 +85,44 @@
 /** Standard easing for Swipe To Reveal. */
 internal val STANDARD_IN_OUT = CubicBezierEasing(0.20f, 0.0f, 0.0f, 1.00f)
 
-/** Different values which the swipeable modifier can be configured to. */
+/**
+ * Different values that determine the state of the [SwipeToReveal] composable, reflected in
+ * [RevealState.currentValue]. [RevealValue.Covered] is considered the default state where none of
+ * the actions are revealed yet.
+ *
+ * [SwipeToReveal] direction is not localised, with the default being [SwipeDirection.RightToLeft],
+ * and [RevealValue.RightRevealing] and [RevealValue.RightRevealed] correspond to the actions
+ * getting revealed from the right side of the screen. In case swipe direction is set to
+ * [SwipeDirection.Both], actions can also get revealed from the left side of the screen, and in
+ * that case [RevealValue.LeftRevealing] and [RevealValue.LeftRevealed] are used.
+ *
+ * @see [SwipeDirection]
+ */
 @ExperimentalWearFoundationApi
 @JvmInline
-public value class RevealValue private constructor(val value: Int) {
+value class RevealValue private constructor(val value: Int) {
     companion object {
         /**
+         * The value which represents the state in which the whole revealable content is fully
+         * revealed, and they are displayed on the left side of the screen. This also represents the
+         * state in which one of the actions has been triggered/performed.
+         *
+         * This is only used when the swipe direction is set to [SwipeDirection.Both], and the user
+         * swipes from the left side of the screen.
+         */
+        val LeftRevealed = RevealValue(-2)
+
+        /**
+         * The value which represents the state in which all the actions are revealed and the top
+         * content is not being swiped. In this state, none of the actions have been triggered or
+         * performed yet, and they are displayed on the left side of the screen.
+         *
+         * This is only used when the swipe direction is set to [SwipeDirection.Both], and the user
+         * swipes from the left side of the screen.
+         */
+        val LeftRevealing = RevealValue(-1)
+
+        /**
          * The default first value which generally represents the state where the revealable actions
          * have not been revealed yet. In this state, none of the actions have been triggered or
          * performed yet.
@@ -101,15 +133,64 @@
          * The value which represents the state in which all the actions are revealed and the top
          * content is not being swiped. In this state, none of the actions have been triggered or
          * performed yet.
+         *
+         * @deprecated Use [RightRevealing] instead.
          */
+        @Deprecated("Use RightRevealing instead.", ReplaceWith("RightRevealing"))
         val Revealing = RevealValue(1)
 
         /**
          * The value which represents the state in which the whole revealable content is fully
          * revealed. This also represents the state in which one of the actions has been
          * triggered/performed.
+         *
+         * @deprecated Use [RightRevealed] instead.
          */
+        @Deprecated("Use RightRevealed instead.", ReplaceWith("RightRevealed"))
         val Revealed = RevealValue(2)
+
+        /**
+         * The value which represents the state in which all the actions are revealed and the top
+         * content is not being swiped. In this state, none of the actions have been triggered or
+         * performed yet, and they are displayed on the right side of the screen.
+         */
+        val RightRevealing = RevealValue(1)
+
+        /**
+         * The value which represents the state in which the whole revealable content is fully
+         * revealed, and the actions are revealed on the right side of the screen. This also
+         * represents the state in which one of the actions has been triggered/performed.
+         */
+        val RightRevealed = RevealValue(2)
+    }
+}
+
+/**
+ * Different values [SwipeToReveal] composable can reveal the actions from.
+ *
+ * [SwipeDirection] is not localised, with the default being [SwipeDirection.RightToLeft] to prevent
+ * conflict with the system-wide swipe to dismiss gesture in an activity, so it's strongly advised
+ * to respect the default value to avoid conflicting gestures.
+ */
+@ExperimentalWearFoundationApi
+@JvmInline
+value class SwipeDirection private constructor(val value: Int) {
+    companion object {
+        /**
+         * The default value which allows the user to swipe right to left to reveal or execute the
+         * actions. It's strongly advised to respect the default behavior to avoid conflict with the
+         * swipe-to-dismiss gesture.
+         */
+        val RightToLeft = SwipeDirection(0)
+
+        /**
+         * The value which allows the user to swipe in either direction to reveal or execute the
+         * actions. This should not be used if the component is used in an activity as the gesture
+         * might conflict with the swipe-to-dismiss gesture and could be confusing for the users.
+         * This is only supported for rare cases where the current screen does not support swipe to
+         * dismiss.
+         */
+        val Both = SwipeDirection(1)
     }
 }
 
@@ -120,7 +201,7 @@
  */
 @ExperimentalWearFoundationApi
 @JvmInline
-public value class RevealActionType private constructor(val value: Int) {
+value class RevealActionType private constructor(val value: Int) {
     companion object {
         /**
          * Represents the primary action composable of [SwipeToReveal]. This corresponds to the
@@ -151,30 +232,45 @@
  * width of the top content starting from right and ending on left.
  *
  * @param coveredAnchor Anchor for the [RevealValue.Covered] value
- * @param revealingAnchor Anchor for the [RevealValue.Revealing] value
- * @param revealedAnchor Anchor for the [RevealValue.Revealed] value
+ * @param revealingAnchor Anchor for the [RevealValue.LeftRevealing] or [RevealValue.RightRevealing]
+ *   value
+ * @param revealedAnchor Anchor for the [RevealValue.LeftRevealed] or [RevealValue.RightRevealed]
+ *   value
+ * @param swipeDirection The direction in which the content can be swiped. It's strongly advised to
+ *   keep the default [SwipeDirection.RightToLeft] in order to preserve compatibility with the
+ *   system wide swipe to dismiss gesture.
  */
 @ExperimentalWearFoundationApi
-public fun createAnchors(
+fun createAnchors(
     coveredAnchor: Float = 0f,
     revealingAnchor: Float = SwipeToRevealDefaults.revealingRatio,
-    revealedAnchor: Float = 1f
+    revealedAnchor: Float = 1f,
+    swipeDirection: SwipeDirection = SwipeDirection.RightToLeft
 ): Map<RevealValue, Float> {
+    if (swipeDirection == SwipeDirection.Both) {
+        return mapOf(
+            RevealValue.LeftRevealed to -revealedAnchor,
+            RevealValue.LeftRevealing to -revealingAnchor,
+            RevealValue.Covered to coveredAnchor,
+            RevealValue.RightRevealing to revealingAnchor,
+            RevealValue.RightRevealed to revealedAnchor
+        )
+    }
     return mapOf(
         RevealValue.Covered to coveredAnchor,
-        RevealValue.Revealing to revealingAnchor,
-        RevealValue.Revealed to revealedAnchor
+        RevealValue.RightRevealing to revealingAnchor,
+        RevealValue.RightRevealed to revealedAnchor
     )
 }
 
 /**
- * A class to keep track of the state of the composable. It can be used to customise the behaviour
+ * A class to keep track of the state of the composable. It can be used to customise the behavior
  * and state of the composable.
  *
  * @constructor Create a [RevealState].
  */
 @ExperimentalWearFoundationApi
-public class RevealState
+class RevealState
 internal constructor(
     initialValue: RevealValue,
     animationSpec: AnimationSpec<Float>,
@@ -182,7 +278,7 @@
     positionalThreshold: Density.(totalDistance: Float) -> Float,
     internal val anchors: Map<RevealValue, Float>,
     internal val coroutineScope: CoroutineScope,
-    internal val nestedScrollDispatcher: NestedScrollDispatcher
+    internal val nestedScrollDispatcher: NestedScrollDispatcher,
 ) {
     /** [SwipeableV2State] internal instance for the state. */
     internal val swipeableState =
@@ -193,17 +289,17 @@
                 confirmValueChangeAndReset(confirmValueChange, revealValue)
             },
             positionalThreshold = positionalThreshold,
-            nestedScrollDispatcher = nestedScrollDispatcher
+            nestedScrollDispatcher = nestedScrollDispatcher,
         )
 
-    public var lastActionType by mutableStateOf(RevealActionType.None)
+    var lastActionType by mutableStateOf(RevealActionType.None)
 
     /**
      * The current [RevealValue] based on the status of the component.
      *
      * @see Modifier.swipeableV2
      */
-    public val currentValue: RevealValue
+    val currentValue: RevealValue
         get() = swipeableState.currentValue
 
     /**
@@ -213,7 +309,7 @@
      *
      * @see Modifier.swipeableV2
      */
-    public val targetValue: RevealValue
+    val targetValue: RevealValue
         get() = swipeableState.targetValue
 
     /**
@@ -221,7 +317,7 @@
      *
      * @see Modifier.swipeableV2
      */
-    public val isAnimationRunning: Boolean
+    val isAnimationRunning: Boolean
         get() = swipeableState.isAnimationRunning
 
     /**
@@ -229,7 +325,7 @@
      *
      * @see Modifier.swipeableV2
      */
-    public val offset: Float
+    val offset: Float
         get() = swipeableState.offset ?: 0f
 
     /**
@@ -239,7 +335,7 @@
      *
      * @see Modifier.swipeableV2
      */
-    public val swipeAnchors: Map<RevealValue, Float>
+    val swipeAnchors: Map<RevealValue, Float>
         get() = anchors
 
     /**
@@ -248,7 +344,7 @@
      * @param targetValue The target [RevealValue] where the [currentValue] will be changed to.
      * @see Modifier.swipeableV2
      */
-    public suspend fun snapTo(targetValue: RevealValue) {
+    suspend fun snapTo(targetValue: RevealValue) {
         // Cover the previously open component if revealing a different one
         if (targetValue != RevealValue.Covered) {
             resetLastState(this)
@@ -261,7 +357,7 @@
      *
      * @param targetValue The target [RevealValue] where the [currentValue] will animate to.
      */
-    public suspend fun animateTo(targetValue: RevealValue) {
+    suspend fun animateTo(targetValue: RevealValue) {
         // Cover the previously open component if revealing a different one
         if (targetValue != RevealValue.Covered) {
             resetLastState(this)
@@ -297,12 +393,12 @@
 
     /**
      * Resets last state if a different SwipeToReveal is being moved to new anchor and the last
-     * state is in [RevealValue.Revealing] mode which represents no action has been performed yet.
-     * In [RevealValue.Revealed], the action has been performed and it will not be reset.
+     * state is in [RevealValue.RightRevealing] mode which represents no action has been performed
+     * yet. In [RevealValue.RightRevealed], the action has been performed and it will not be reset.
      */
     private suspend fun resetLastState(currentState: RevealState) {
         val oldState = SingleSwipeCoordinator.lastUpdatedState.getAndSet(currentState)
-        if (currentState != oldState && oldState?.currentValue == RevealValue.Revealing) {
+        if (currentState != oldState && oldState?.currentValue == RevealValue.RightRevealing) {
             oldState.animateTo(RevealValue.Covered)
         }
     }
@@ -329,7 +425,7 @@
  */
 @ExperimentalWearFoundationApi
 @Composable
-public fun rememberRevealState(
+fun rememberRevealState(
     initialValue: RevealValue = RevealValue.Covered,
     animationSpec: AnimationSpec<Float> = SwipeToRevealDefaults.animationSpec,
     confirmValueChange: (RevealValue) -> Boolean = { true },
@@ -347,7 +443,7 @@
             positionalThreshold = positionalThreshold,
             anchors = anchors,
             coroutineScope = coroutineScope,
-            nestedScrollDispatcher = nestedScrollDispatcher
+            nestedScrollDispatcher = nestedScrollDispatcher,
         )
     }
 }
@@ -362,7 +458,7 @@
  * triggered.
  *
  * An optional undo action can also be added. This undo action will be visible to users once the
- * [RevealValue] becomes [RevealValue.Revealed].
+ * [RevealValue] becomes [RevealValue.RightRevealed].
  *
  * It is strongly recommended to have icons represent the actions and maybe a text and icon for the
  * undo action.
@@ -388,12 +484,12 @@
  * @param secondaryAction An optional action that can be added to the component. We strongly
  *   recommend triggering the action when it is clicked.
  * @param undoAction The optional undo action that will be applied to the component once the the
- *   [RevealState.currentValue] becomes [RevealValue.Revealed].
+ *   [RevealState.currentValue] becomes [RevealValue.RightRevealed].
  * @param content The content that will be initially displayed over the other actions provided.
  */
 @ExperimentalWearFoundationApi
 @Composable
-public fun SwipeToReveal(
+fun SwipeToReveal(
     primaryAction: @Composable RevealScope.() -> Unit,
     modifier: Modifier = Modifier,
     onFullSwipe: () -> Unit = {},
@@ -415,7 +511,7 @@
                 .swipeableV2(
                     state = state.swipeableState,
                     orientation = Orientation.Horizontal,
-                    enabled = state.currentValue != RevealValue.Revealed,
+                    enabled = state.currentValue != RevealValue.RightRevealed,
                 )
                 .swipeAnchors(
                     state = state.swipeableState,
@@ -433,11 +529,14 @@
                 // connection applied before this modifier consume the scroll/fling events.
                 .nestedScroll(noOpNestedScrollConnection, state.nestedScrollDispatcher)
     ) {
-        val swipeCompleted = state.currentValue == RevealValue.Revealed
+        val swipeCompleted =
+            state.currentValue == RevealValue.RightRevealed ||
+                state.currentValue == RevealValue.LeftRevealed
         val lastActionIsSecondary = state.lastActionType == RevealActionType.SecondaryAction
         val isWithinRevealOffset by remember {
             derivedStateOf { abs(state.offset) <= revealScope.revealOffset }
         }
+        val canSwipeRight = (state.swipeAnchors.minOfOrNull { (_, offset) -> offset } ?: 0f) < 0f
 
         // Determines whether the secondary action will be visible based on the current
         // reveal offset
@@ -447,13 +546,21 @@
         // when secondary action is clicked
         val hideActions = !isWithinRevealOffset && lastActionIsSecondary
 
-        val shouldDrawActions by remember { derivedStateOf { abs(state.offset) > 0 } }
+        val swipingRight by remember { derivedStateOf { state.offset > 0 } }
+
+        // Don't draw actions on the left side if the user cannot swipe right, and they are
+        // currently swiping right
+        val shouldDrawActions by remember {
+            derivedStateOf { abs(state.offset) > 0 && (canSwipeRight || !swipingRight) }
+        }
 
         // Draw the buttons only when offset is greater than zero.
         if (shouldDrawActions) {
             Box(
                 modifier = Modifier.matchParentSize(),
-                contentAlignment = AbsoluteAlignment.CenterRight
+                contentAlignment =
+                    if (swipingRight) AbsoluteAlignment.CenterLeft
+                    else AbsoluteAlignment.CenterRight
             ) {
                 AnimatedContent(
                     targetState = swipeCompleted && undoAction != null,
@@ -544,22 +651,41 @@
                                     },
                             horizontalArrangement = Arrangement.Absolute.Right
                         ) {
-                            // weight cannot be 0 so remove the composable when weight becomes 0
-                            if (secondaryAction != null && secondaryActionWeight.value > 0) {
+                            if (!swipingRight) {
+                                // weight cannot be 0 so remove the composable when weight becomes 0
+                                if (secondaryAction != null && secondaryActionWeight.value > 0) {
+                                    Spacer(Modifier.size(SwipeToRevealDefaults.padding))
+                                    ActionSlot(
+                                        revealScope,
+                                        weight = secondaryActionWeight.value,
+                                        opacity = secondaryActionAlpha,
+                                        content = secondaryAction,
+                                    )
+                                }
                                 Spacer(Modifier.size(SwipeToRevealDefaults.padding))
                                 ActionSlot(
                                     revealScope,
-                                    weight = secondaryActionWeight.value,
-                                    opacity = secondaryActionAlpha,
-                                    content = secondaryAction,
+                                    content = primaryAction,
+                                    opacity = primaryActionAlpha
                                 )
+                            } else {
+                                ActionSlot(
+                                    revealScope,
+                                    content = primaryAction,
+                                    opacity = primaryActionAlpha
+                                )
+                                Spacer(Modifier.size(SwipeToRevealDefaults.padding))
+                                // weight cannot be 0 so remove the composable when weight becomes 0
+                                if (secondaryAction != null && secondaryActionWeight.value > 0) {
+                                    ActionSlot(
+                                        revealScope,
+                                        weight = secondaryActionWeight.value,
+                                        opacity = secondaryActionAlpha,
+                                        content = secondaryAction,
+                                    )
+                                    Spacer(Modifier.size(SwipeToRevealDefaults.padding))
+                                }
                             }
-                            Spacer(Modifier.size(SwipeToRevealDefaults.padding))
-                            ActionSlot(
-                                revealScope,
-                                content = primaryAction,
-                                opacity = primaryActionAlpha
-                            )
                         }
                     }
                 }
@@ -568,14 +694,15 @@
         Row(
             modifier =
                 Modifier.absoluteOffset {
-                    IntOffset(x = state.requireOffset().roundToInt().coerceAtMost(0), y = 0)
+                    val xOffset = state.requireOffset().roundToInt()
+                    IntOffset(x = if (canSwipeRight) xOffset else xOffset.coerceAtMost(0), y = 0)
                 }
         ) {
             content()
         }
         LaunchedEffect(state.currentValue) {
             if (
-                state.currentValue == RevealValue.Revealed &&
+                state.currentValue == RevealValue.RightRevealed &&
                     state.lastActionType == RevealActionType.None
             ) {
                 onFullSwipe()
@@ -585,22 +712,22 @@
 }
 
 @ExperimentalWearFoundationApi
-public interface RevealScope {
+interface RevealScope {
 
     /**
      * The offset, in pixels, where the revealed actions are fully visible but the existing content
      * would be left in place if the reveal action was stopped. This offset is used to create the
-     * anchor for [RevealValue.Revealing]. If there is no such anchor defined for
-     * [RevealValue.Revealing], it returns 0.0f.
+     * anchor for [RevealValue.RightRevealing]. If there is no such anchor defined for
+     * [RevealValue.RightRevealing], it returns 0.0f.
      */
     /* @FloatRange(from = 0.0) */
-    public val revealOffset: Float
+    val revealOffset: Float
 
     /**
      * The last [RevealActionType] that was set in [RevealState]. This may not be set if the state
      * changed via interaction and not through API call.
      */
-    public val lastActionType: RevealActionType
+    val lastActionType: RevealActionType
 }
 
 @OptIn(ExperimentalWearFoundationApi::class)
@@ -616,7 +743,7 @@
     val width = mutableFloatStateOf(0.0f)
 
     override val revealOffset: Float
-        get() = width.floatValue * (revealState.swipeAnchors[RevealValue.Revealing] ?: 0.0f)
+        get() = width.floatValue * (revealState.swipeAnchors[RevealValue.RightRevealing] ?: 0.0f)
 
     override val lastActionType: RevealActionType
         get() = revealState.lastActionType
@@ -632,8 +759,8 @@
     internal val padding = 2.dp
 
     /**
-     * Default ratio of the content displayed when in [RevealValue.Revealing] state, i.e. all the
-     * actions are revealed and the top content is not being swiped. For example, a value of 0.7
+     * Default ratio of the content displayed when in [RevealValue.RightRevealing] state, i.e. all
+     * the actions are revealed and the top content is not being swiped. For example, a value of 0.7
      * means that 70% of the width is used to place the actions.
      */
     internal const val revealingRatio = 0.7f
@@ -642,9 +769,9 @@
      * Default position threshold that needs to be swiped in order to transition to the next state.
      * Used in conjunction with [revealingRatio]; for example, a threshold of 0.5 with a revealing
      * ratio of 0.7 means that the user needs to swipe at least 35% (0.5 * 0.7) of the component
-     * width to go from [RevealValue.Covered] to [RevealValue.Revealing] and at least 85% (0.7 +
-     * 0.5 * (1 - 0.7)) of the component width to go from [RevealValue.Revealing] to
-     * [RevealValue.Revealed].
+     * width to go from [RevealValue.Covered] to [RevealValue.RightRevealing] and at least 85%
+     * (0.7 + 0.5 * (1 - 0.7)) of the component width to go from [RevealValue.RightRevealing] to
+     * [RevealValue.RightRevealed].
      */
     internal val positionalThreshold = fractionalPositionalThreshold(0.5f)
 }
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
index 6dd1581a..0b23556 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
@@ -56,7 +56,7 @@
     fun swipeToRevealCard_singleAction() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealCard(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing),
                 secondaryAction = null
             )
         }
@@ -66,7 +66,7 @@
     fun swipeToRevealChip_singleAction() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealChip(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing),
                 secondaryAction = null
             )
         }
@@ -76,7 +76,7 @@
     fun swipeToRevealCard_twoActions() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealCard(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             )
         }
     }
@@ -85,7 +85,7 @@
     fun swipeToRevealChip_twoActions() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealChip(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             )
         }
     }
@@ -94,7 +94,7 @@
     fun swipeToRevealChip_undoPrimaryAction() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealChip(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
     }
@@ -103,7 +103,7 @@
     fun swipeToRevealCard_undoPrimaryAction() {
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             swipeToRevealCard(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
     }
@@ -113,7 +113,7 @@
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             val revealState = rememberRevealState()
             val coroutineScope = rememberCoroutineScope()
-            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+            coroutineScope.launch { revealState.animateTo(RevealValue.RightRevealed) }
             revealState.lastActionType = RevealActionType.SecondaryAction
             swipeToRevealChip(revealState = revealState)
         }
@@ -124,7 +124,7 @@
         rule.verifyScreenshot(screenshotRule = screenshotRule, methodName = testName.methodName) {
             val revealState = rememberRevealState()
             val coroutineScope = rememberCoroutineScope()
-            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+            coroutineScope.launch { revealState.animateTo(RevealValue.RightRevealed) }
             revealState.lastActionType = RevealActionType.SecondaryAction
             swipeToRevealCard(revealState = revealState)
         }
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
index 80ce95b..686e659 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
@@ -97,7 +97,7 @@
     fun whenRevealing_actionsExist_inChip() {
         rule.setContentWithTheme {
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             )
         }
         rule.onNodeWithTag(PRIMARY_ACTION_TAG).assertExists()
@@ -108,7 +108,7 @@
     fun whenRevealing_actionsExist_inCard() {
         rule.setContentWithTheme {
             swipeToRevealCardDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             )
         }
         rule.onNodeWithTag(PRIMARY_ACTION_TAG).assertExists()
@@ -119,7 +119,7 @@
     fun whenRevealed_undoActionExists_inChip() {
         rule.setContentWithTheme {
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
 
@@ -130,7 +130,7 @@
     fun whenRevealed_undoActionExists_inCard() {
         rule.setContentWithTheme {
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
 
@@ -141,7 +141,7 @@
     fun whenRevealed_actionsDoNotExist_inChip() {
         rule.setContentWithTheme {
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
 
@@ -153,7 +153,7 @@
     fun whenRevealed_actionsDoNotExist_inCard() {
         rule.setContentWithTheme {
             swipeToRevealCardDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealed)
             )
         }
 
@@ -165,7 +165,7 @@
     fun onPrimaryActionClick_triggersOnClick_forChip() {
         var clicked = false
         rule.setContentWithTheme {
-            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            val revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             swipeToRevealChipDefault(
                 revealState = revealState,
                 primaryAction = {
@@ -182,7 +182,7 @@
     fun onSecondaryActionClick_triggersOnClick_forChip() {
         var clicked = false
         rule.setContentWithTheme {
-            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            val revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             swipeToRevealChipDefault(
                 revealState = revealState,
                 secondaryAction = {
@@ -198,7 +198,7 @@
     @Test
     fun onPrimaryActionClickWithStateToRevealed_undoPrimaryActionCanBeClicked() {
         rule.setContentWithTheme {
-            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            val revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
@@ -206,7 +206,9 @@
                     createPrimaryAction(
                         revealState = revealState,
                         onClick = {
-                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.RightRevealed)
+                            }
                         }
                     )
                 },
@@ -225,7 +227,7 @@
     fun onPrimaryActionClickAndPrimaryUndoClicked_stateChangesToCovered() {
         lateinit var revealState: RevealState
         rule.setContentWithTheme {
-            revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
@@ -233,7 +235,9 @@
                     createPrimaryAction(
                         revealState = revealState,
                         onClick = {
-                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.RightRevealed)
+                            }
                         }
                     )
                 },
@@ -266,7 +270,7 @@
     @Test
     fun onSecondaryActionClickWithStateToRevealed_undoSecondaryActionCanBeClicked() {
         rule.setContentWithTheme {
-            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            val revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
@@ -274,7 +278,9 @@
                     createSecondaryAction(
                         revealState = revealState,
                         onClick = {
-                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.RightRevealed)
+                            }
                         }
                     )
                 },
@@ -293,7 +299,7 @@
     fun onSecondaryActionClickAndUndoSecondaryClicked_stateChangesToCovered() {
         lateinit var revealState: RevealState
         rule.setContentWithTheme {
-            revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
@@ -301,7 +307,9 @@
                     createSecondaryAction(
                         revealState = revealState,
                         onClick = {
-                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.RightRevealed)
+                            }
                         }
                     )
                 },
@@ -341,7 +349,7 @@
             primaryActionColor = MaterialTheme.colors.error
             secondaryActionColor = MaterialTheme.colors.surface
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing)
             )
         }
 
@@ -362,7 +370,7 @@
         val overrideSecondaryActionColor = Color.Green
         rule.setContentWithTheme {
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                revealState = rememberRevealState(initialValue = RevealValue.RightRevealing),
                 colors =
                     SwipeToRevealDefaults.actionColors(
                         primaryActionBackgroundColor = overridePrimaryActionColor,
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 308b0e6..7dd366a 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -6,20 +6,19 @@
     method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void GroupSeparator();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues confirmDismissContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
-    method public float getEdgeButtonExtraTopPadding();
     method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
     property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
-    property public final float edgeButtonExtraTopPadding;
     field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
   }
 
   public final class AlertDialogKt {
-    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
     method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
   }
 
@@ -453,16 +452,12 @@
   }
 
   public final class EdgeButtonKt {
-    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float preferredHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   @SuppressCompatibility @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 {
   }
 
-  public final class HorizontalPageIndicatorKt {
-    method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(int pageCount, int currentPage, kotlin.jvm.functions.Function0<java.lang.Float> currentPageOffsetFraction, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional float indicatorSize, optional float spacing);
-  }
-
   @androidx.compose.runtime.Immutable public final class IconButtonColors {
     ctor public IconButtonColors(long containerColor, long contentColor, long disabledContainerColor, long disabledContentColor);
     method public long getContainerColor();
@@ -704,6 +699,21 @@
     method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
+  public final class PageIndicatorDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getBackgroundColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getSelectedColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getUnselectedColor();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long backgroundColor;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long selectedColor;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long unselectedColor;
+    field public static final androidx.wear.compose.material3.PageIndicatorDefaults INSTANCE;
+  }
+
+  public final class PageIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(androidx.wear.compose.foundation.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional long backgroundColor);
+    method @androidx.compose.runtime.Composable public static void VerticalPageIndicator(androidx.wear.compose.foundation.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional long backgroundColor);
+  }
+
   public final class PickerDefaults {
     method public float getGradientRatio();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior rotarySnapBehavior(androidx.wear.compose.material3.PickerState state);
@@ -1185,7 +1195,7 @@
 
   public final class SwipeToRevealKt {
     method @androidx.compose.runtime.Composable public static void SwipeToReveal(kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SwipeToRevealScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.RevealState revealState, optional float actionButtonHeight, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional float anchorWidth, optional boolean useAnchoredActions);
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional float anchorWidth, optional boolean useAnchoredActions, optional int swipeDirection);
   }
 
   public final class SwipeToRevealScope {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 308b0e6..7dd366a 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -6,20 +6,19 @@
     method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void GroupSeparator();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues confirmDismissContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
-    method public float getEdgeButtonExtraTopPadding();
     method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
     property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
-    property public final float edgeButtonExtraTopPadding;
     field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
   }
 
   public final class AlertDialogKt {
-    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
     method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
   }
 
@@ -453,16 +452,12 @@
   }
 
   public final class EdgeButtonKt {
-    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float preferredHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   @SuppressCompatibility @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 {
   }
 
-  public final class HorizontalPageIndicatorKt {
-    method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(int pageCount, int currentPage, kotlin.jvm.functions.Function0<java.lang.Float> currentPageOffsetFraction, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional float indicatorSize, optional float spacing);
-  }
-
   @androidx.compose.runtime.Immutable public final class IconButtonColors {
     ctor public IconButtonColors(long containerColor, long contentColor, long disabledContainerColor, long disabledContentColor);
     method public long getContainerColor();
@@ -704,6 +699,21 @@
     method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
+  public final class PageIndicatorDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getBackgroundColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getSelectedColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getUnselectedColor();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long backgroundColor;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long selectedColor;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long unselectedColor;
+    field public static final androidx.wear.compose.material3.PageIndicatorDefaults INSTANCE;
+  }
+
+  public final class PageIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(androidx.wear.compose.foundation.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional long backgroundColor);
+    method @androidx.compose.runtime.Composable public static void VerticalPageIndicator(androidx.wear.compose.foundation.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional long selectedColor, optional long unselectedColor, optional long backgroundColor);
+  }
+
   public final class PickerDefaults {
     method public float getGradientRatio();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior rotarySnapBehavior(androidx.wear.compose.material3.PickerState state);
@@ -1185,7 +1195,7 @@
 
   public final class SwipeToRevealKt {
     method @androidx.compose.runtime.Composable public static void SwipeToReveal(kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SwipeToRevealScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.RevealState revealState, optional float actionButtonHeight, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional float anchorWidth, optional boolean useAnchoredActions);
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.RevealState rememberRevealState(optional int initialValue, optional float anchorWidth, optional boolean useAnchoredActions, optional int swipeDirection);
   }
 
   public final class SwipeToRevealScope {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
index 4440425..c8618c6 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
@@ -23,6 +23,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.rounded.AccountCircle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -41,6 +44,8 @@
 import androidx.wear.compose.material3.AlertDialog
 import androidx.wear.compose.material3.AlertDialogDefaults
 import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.ListHeader
 import androidx.wear.compose.material3.MaterialTheme
 import androidx.wear.compose.material3.RadioButton
@@ -56,6 +61,7 @@
         ComposableDemo("Bottom button") { AlertDialogWithBottomButtonSample() },
         ComposableDemo("Confirm and Dismiss") { AlertDialogWithConfirmAndDismissSample() },
         ComposableDemo("Content groups") { AlertDialogWithContentGroupsSample() },
+        ComposableDemo("Button stack") { AlertDialogWithButtonStack() },
         ComposableDemo("AlertDialog builder") { AlertDialogBuilder() },
     )
 
@@ -67,7 +73,7 @@
     var showMessage by remember { mutableStateOf(false) }
     var showSecondaryButton by remember { mutableStateOf(false) }
     var showCaption by remember { mutableStateOf(false) }
-    var showTwoButtons by remember { mutableStateOf(false) }
+    var buttonsType by remember { mutableStateOf(AlertButtonsType.BOTTOM_BUTTON) }
 
     var showDialog by remember { mutableStateOf(false) }
 
@@ -121,19 +127,27 @@
             item {
                 RadioButton(
                     modifier = Modifier.fillMaxWidth(),
-                    selected = !showTwoButtons,
-                    onSelect = { showTwoButtons = false },
+                    selected = buttonsType == AlertButtonsType.BOTTOM_BUTTON,
+                    onSelect = { buttonsType = AlertButtonsType.BOTTOM_BUTTON },
                     label = { Text("Single Bottom button") },
                 )
             }
             item {
                 RadioButton(
                     modifier = Modifier.fillMaxWidth(),
-                    selected = showTwoButtons,
-                    onSelect = { showTwoButtons = true },
+                    selected = buttonsType == AlertButtonsType.CONFIRM_DISMISS,
+                    onSelect = { buttonsType = AlertButtonsType.CONFIRM_DISMISS },
                     label = { Text("Ok/Cancel buttons") },
                 )
             }
+            item {
+                RadioButton(
+                    modifier = Modifier.fillMaxWidth(),
+                    selected = buttonsType == AlertButtonsType.NO_BUTTONS,
+                    onSelect = { buttonsType = AlertButtonsType.NO_BUTTONS },
+                    label = { Text("No bottom button") },
+                )
+            }
             item { Button(onClick = { showDialog = true }, label = { Text("Show dialog") }) }
         }
     }
@@ -145,7 +159,7 @@
             showCaption = showCaption,
             showSecondaryButton = showSecondaryButton,
             showMessage = showMessage,
-            showTwoButtons = showTwoButtons,
+            buttonsType = buttonsType,
             onConfirmButton = { showDialog = false },
             onDismissRequest = { showDialog = false }
         )
@@ -153,6 +167,60 @@
 }
 
 @Composable
+fun AlertDialogWithButtonStack() {
+    var showDialog by remember { mutableStateOf(false) }
+
+    Box(Modifier.fillMaxSize()) {
+        FilledTonalButton(
+            modifier = Modifier.align(Alignment.Center),
+            onClick = { showDialog = true },
+            label = { Text("Show Dialog") }
+        )
+    }
+
+    AlertDialog(
+        show = showDialog,
+        onDismissRequest = { showDialog = false },
+        icon = {
+            Icon(
+                Icons.Rounded.AccountCircle,
+                modifier = Modifier.size(32.dp),
+                contentDescription = null,
+                tint = MaterialTheme.colorScheme.primary
+            )
+        },
+        title = { Text("Allow access to your photos?") },
+        text = { Text("Lerp ipsum dolor sit amet.") },
+        bottomButton = null,
+    ) {
+        item {
+            Button(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("While using app") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+        item {
+            FilledTonalButton(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("Ask every time") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+        item {
+            FilledTonalButton(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("Don't allow") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+    }
+}
+
+@Composable
 private fun CustomAlertDialog(
     show: Boolean,
     onDismissRequest: () -> Unit,
@@ -160,7 +228,7 @@
     properties: DialogProperties = DialogProperties(),
     showIcon: Boolean,
     onConfirmButton: () -> Unit,
-    showTwoButtons: Boolean,
+    buttonsType: AlertButtonsType,
     showMessage: Boolean,
     showSecondaryButton: Boolean,
     showCaption: Boolean,
@@ -183,15 +251,15 @@
                 { Message() }
             } else null,
         onConfirmButton =
-            if (showTwoButtons) {
+            if (buttonsType == AlertButtonsType.CONFIRM_DISMISS) {
                 onConfirmButton
             } else null,
         onDismissButton =
-            if (showTwoButtons) {
+            if (buttonsType == AlertButtonsType.CONFIRM_DISMISS) {
                 { /* dismiss action */ }
             } else null,
         onBottomButton =
-            if (!showTwoButtons) {
+            if (buttonsType == AlertButtonsType.BOTTOM_BUTTON) {
                 onConfirmButton
             } else null,
         content =
@@ -202,7 +270,7 @@
                     }
                     if (showCaption) {
                         item { Caption(captionHorizontalPadding) }
-                        if (!showTwoButtons) {
+                        if (buttonsType == AlertButtonsType.BOTTOM_BUTTON) {
                             item { AlertDialogDefaults.GroupSeparator() }
                         }
                     }
@@ -279,7 +347,7 @@
             dismissButton = { AlertDialogDefaults.DismissButton(onDismissButton) },
             content = content
         )
-    } else if (onBottomButton != null) {
+    } else
         AlertDialog(
             show = show,
             onDismissRequest = onDismissRequest,
@@ -288,8 +356,16 @@
             title = title,
             icon = icon,
             text = message,
-            bottomButton = { AlertDialogDefaults.BottomButton(onBottomButton) },
+            bottomButton =
+                if (onBottomButton != null) {
+                    { AlertDialogDefaults.BottomButton(onBottomButton) }
+                } else null,
             content = content
         )
-    }
+}
+
+private enum class AlertButtonsType {
+    NO_BUTTONS,
+    BOTTOM_BUTTON,
+    CONFIRM_DISMISS
 }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
index d93f73d..ba4a754 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
@@ -72,7 +72,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                    preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                     colors = ButtonDefaults.buttonColors(containerColor = Color.DarkGray)
                 ) {
                     Text(labels[selectedLabel.intValue], color = Color.White)
@@ -120,7 +120,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                    preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                     colors = ButtonDefaults.buttonColors(containerColor = Color.DarkGray)
                 ) {
                     Text(labels[selectedLabel.intValue], color = Color.White)
@@ -215,7 +215,7 @@
             EdgeButton(
                 onClick = {},
                 enabled = colorNames[color] != "D",
-                buttonHeight = sizes[size],
+                preferredHeight = sizes[size],
                 colors = colors[color],
                 border =
                     if (colorNames[color] == "O")
@@ -257,7 +257,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = sizes[selectedSize].second,
+                    preferredHeight = sizes[selectedSize].second,
                     colors = colors[selectedColor].second,
                     border =
                         if (colors[selectedColor].first == "Outlined")
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
similarity index 65%
rename from wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
rename to wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
index 8a9bbea..4ecf02f 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
@@ -16,14 +16,29 @@
 
 package androidx.wear.compose.material3.demos
 
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
 import android.view.HapticFeedbackConstants
 import android.view.View
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.ListHeader
 import androidx.wear.compose.material3.Text
 
@@ -63,7 +78,26 @@
             Pair(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, "Virtual Key Release"),
         )
 
+    val premiumVibratorEnabled = isPremiumVibratorEnabled()
+
     ScalingLazyDemo {
+        item { ListHeader { Text("Premium Haptics") } }
+        item {
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.Center,
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                Icon(
+                    imageVector =
+                        if (premiumVibratorEnabled) Icons.Filled.Check else Icons.Filled.Close,
+                    contentDescription = "Premium Haptics Status",
+                    tint = if (premiumVibratorEnabled) Color.Green else Color.Red
+                )
+                Spacer(modifier = Modifier.width(8.dp))
+                Text(if (premiumVibratorEnabled) "Enabled" else "Disabled")
+            }
+        }
         item { ListHeader { Text("Haptic Constants") } }
         items(hapticConstants.size) { index ->
             val (constant, name) = hapticConstants[index]
@@ -92,3 +126,23 @@
         view.performHapticFeedback(feedbackConstant)
     }
 }
+
+@Composable
+fun isPremiumVibratorEnabled(): Boolean {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+        // NB whilst the 'areAllPrimitivesSupported' API needs R (API 30), we need S (API
+        // 31) so that PRIMITIVE_THUD is available.
+        val vibrator = LocalContext.current.getSystemService(Vibrator::class.java)
+        if (
+            vibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_THUD
+            )
+        ) {
+            return true
+        }
+    }
+
+    return false
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SwipeToRevealDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SwipeToRevealDemo.kt
index 0c777e61..85b99df 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SwipeToRevealDemo.kt
@@ -28,6 +28,7 @@
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Lock
 import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.MoreVert
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
@@ -40,6 +41,7 @@
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
 import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.SwipeDirection
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.Card
@@ -54,6 +56,71 @@
 
 @OptIn(ExperimentalWearFoundationApi::class)
 @Composable
+fun SwipeToRevealBothDirectionsNonAnchoring() {
+    SwipeToReveal(
+        revealState =
+            rememberRevealState(
+                swipeDirection = SwipeDirection.Both,
+                useAnchoredActions = false,
+            ),
+        actions = {
+            primaryAction(
+                onClick = { /* This block is called when the primary action is executed. */ },
+                icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
+                label = "Delete"
+            )
+            undoPrimaryAction(
+                onClick = { /* This block is called when the undo primary action is executed. */ },
+                label = "Undo Delete"
+            )
+        }
+    ) {
+        Button(modifier = Modifier.fillMaxWidth(), onClick = {}) {
+            Text("This Button has only one action", modifier = Modifier.fillMaxSize())
+        }
+    }
+}
+
+@OptIn(ExperimentalWearFoundationApi::class)
+@Composable
+fun SwipeToRevealBothDirections() {
+    SwipeToReveal(
+        revealState =
+            rememberRevealState(
+                // Use the double action anchor width when revealing two actions
+                anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth,
+                swipeDirection = SwipeDirection.Both
+            ),
+        actions = {
+            primaryAction(
+                onClick = { /* This block is called when the primary action is executed. */ },
+                icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
+                label = "Delete"
+            )
+            secondaryAction(
+                onClick = { /* This block is called when the secondary action is executed. */ },
+                icon = { Icon(Icons.Outlined.MoreVert, contentDescription = "More") },
+                label = "More"
+            )
+            undoPrimaryAction(
+                onClick = { /* This block is called when the undo primary action is executed. */ },
+                label = "Undo Delete"
+            )
+            undoSecondaryAction(
+                onClick = { /* This block is called when the undo secondary action is executed. */
+                },
+                label = "Undo Secondary"
+            )
+        }
+    ) {
+        Button(modifier = Modifier.fillMaxWidth(), onClick = {}) {
+            Text("This Button has two actions", modifier = Modifier.fillMaxSize())
+        }
+    }
+}
+
+@OptIn(ExperimentalWearFoundationApi::class)
+@Composable
 fun SwipeToRevealTwoActionsWithUndo() {
     val context = LocalContext.current
     val showToasts = remember { mutableStateOf(true) }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
index 92d8d87..20d4ca7 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
@@ -18,11 +18,8 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
@@ -32,7 +29,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
 import androidx.wear.compose.integration.demos.common.ComposableDemo
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.Icon
@@ -71,16 +67,14 @@
             initialTime = timePickerTime
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
index 37bfa5b..8c634101c 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.compose.material3.demos
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
@@ -52,7 +53,8 @@
         ComposableDemo("Clock with Icon") { TimeTextWithIcon() },
         ComposableDemo("Clock with custom colors") { TimeTextWithCustomColors() },
         ComposableDemo("Clock with custom font size") { TimeTextCustomSize() },
-        ComposableDemo("Clock on list") { TimeTextOnScreen() }
+        ComposableDemo("Clock on list") { TimeTextOnScreen() },
+        ComposableDemo("Clock on white background") { TimeTextOnScreenWhiteBackground() }
     )
 
 @Composable
@@ -150,3 +152,8 @@
         TimeText { time() }
     }
 }
+
+@Composable
+fun TimeTextOnScreenWhiteBackground() {
+    Box(Modifier.fillMaxSize().background(Color.White)) { TimeText { time() } }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TypographyDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TypographyDemo.kt
index 3be3531..d2db0be 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TypographyDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TypographyDemo.kt
@@ -71,7 +71,7 @@
                 ComposableDemo("Display Small") {
                     Centralize {
                         Text(
-                            "Display Small",
+                            "Display\nSmall",
                             textAlign = TextAlign.Center,
                             style = MaterialTheme.typography.displaySmall
                         )
@@ -80,7 +80,7 @@
                 ComposableDemo("Display Medium") {
                     Centralize {
                         Text(
-                            "Display Medium",
+                            "Display\nMedium",
                             textAlign = TextAlign.Center,
                             style = MaterialTheme.typography.displayMedium
                         )
@@ -89,7 +89,7 @@
                 ComposableDemo("Display Large") {
                     Centralize {
                         Text(
-                            "Display Large",
+                            "Display\nLarge",
                             textAlign = TextAlign.Center,
                             style = MaterialTheme.typography.displayLarge
                         )
@@ -100,28 +100,68 @@
         ComposableDemo("Title") {
             Centralize {
                 Column(horizontalAlignment = Alignment.CenterHorizontally) {
-                    Text("Title Small", style = MaterialTheme.typography.titleSmall)
-                    Text("Title Medium", style = MaterialTheme.typography.titleMedium)
-                    Text("Title Large", style = MaterialTheme.typography.titleLarge)
+                    Text(
+                        "Title\nSmall",
+                        style = MaterialTheme.typography.titleSmall,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Title\nMedium",
+                        style = MaterialTheme.typography.titleMedium,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Title\nLarge",
+                        style = MaterialTheme.typography.titleLarge,
+                        textAlign = TextAlign.Center
+                    )
                 }
             }
         },
         ComposableDemo("Label") {
             Centralize {
                 Column(horizontalAlignment = Alignment.CenterHorizontally) {
-                    Text("Label Small", style = MaterialTheme.typography.labelSmall)
-                    Text("Label Medium", style = MaterialTheme.typography.labelMedium)
-                    Text("Label Large", style = MaterialTheme.typography.labelLarge)
+                    Text(
+                        "Label\nSmall",
+                        style = MaterialTheme.typography.labelSmall,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Label\nMedium",
+                        style = MaterialTheme.typography.labelMedium,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Label\nLarge",
+                        style = MaterialTheme.typography.labelLarge,
+                        textAlign = TextAlign.Center
+                    )
                 }
             }
         },
         ComposableDemo("Body") {
             Centralize {
                 Column(horizontalAlignment = Alignment.CenterHorizontally) {
-                    Text("Body Extra Small", style = MaterialTheme.typography.bodyExtraSmall)
-                    Text("Body Small", style = MaterialTheme.typography.bodySmall)
-                    Text("Body Medium", style = MaterialTheme.typography.bodyMedium)
-                    Text("Body Large", style = MaterialTheme.typography.bodyLarge)
+                    Text(
+                        "Body\nExtra\nSmall",
+                        style = MaterialTheme.typography.bodyExtraSmall,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Body\nSmall",
+                        style = MaterialTheme.typography.bodySmall,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Body\nMedium",
+                        style = MaterialTheme.typography.bodyMedium,
+                        textAlign = TextAlign.Center
+                    )
+                    Text(
+                        "Body\nLarge",
+                        style = MaterialTheme.typography.bodyLarge,
+                        textAlign = TextAlign.Center
+                    )
                 }
             }
         },
@@ -129,19 +169,25 @@
             "Numeral",
             listOf(
                 ComposableDemo("Extra Small") {
-                    Centralize { Text("0123", style = MaterialTheme.typography.numeralExtraSmall) }
+                    Centralize {
+                        Text("0123\n6789", style = MaterialTheme.typography.numeralExtraSmall)
+                    }
                 },
                 ComposableDemo("Small") {
-                    Centralize { Text("0123", style = MaterialTheme.typography.numeralSmall) }
+                    Centralize { Text("0123\n6789", style = MaterialTheme.typography.numeralSmall) }
                 },
                 ComposableDemo("Medium") {
-                    Centralize { Text("0123", style = MaterialTheme.typography.numeralMedium) }
+                    Centralize {
+                        Text("0123\n6789", style = MaterialTheme.typography.numeralMedium)
+                    }
                 },
                 ComposableDemo("Large") {
-                    Centralize { Text("0123", style = MaterialTheme.typography.numeralLarge) }
+                    Centralize { Text("0123\n6789", style = MaterialTheme.typography.numeralLarge) }
                 },
                 ComposableDemo("Extra Large") {
-                    Centralize { Text("0123", style = MaterialTheme.typography.numeralExtraLarge) }
+                    Centralize {
+                        Text("0123\n6789", style = MaterialTheme.typography.numeralExtraLarge)
+                    }
                 }
             )
         ),
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index fb753ac..079c6ce 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -30,8 +30,9 @@
 import androidx.wear.compose.material3.samples.EdgeButtonSample
 import androidx.wear.compose.material3.samples.EdgeSwipeForSwipeToDismiss
 import androidx.wear.compose.material3.samples.FixedFontSize
-import androidx.wear.compose.material3.samples.HorizontalPageIndicatorSample
 import androidx.wear.compose.material3.samples.HorizontalPageIndicatorWithPagerSample
+import androidx.wear.compose.material3.samples.LazyColumnScalingMorphingEffectSample
+import androidx.wear.compose.material3.samples.LazyColumnTargetMorphingHeightSample
 import androidx.wear.compose.material3.samples.ScaffoldSample
 import androidx.wear.compose.material3.samples.SimpleSwipeToDismissBox
 import androidx.wear.compose.material3.samples.StatefulSwipeToDismissBox
@@ -41,11 +42,13 @@
 import androidx.wear.compose.material3.samples.SwipeToRevealNonAnchoredSample
 import androidx.wear.compose.material3.samples.SwipeToRevealSample
 import androidx.wear.compose.material3.samples.SwipeToRevealSingleActionCardSample
+import androidx.wear.compose.material3.samples.VerticalPageIndicatorWithPagerSample
 
 val WearMaterial3Demos =
     Material3DemoCategory(
         "Material 3",
         listOf(
+            ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
             Material3DemoCategory(title = "Typography", TypographyDemos),
             Material3DemoCategory(
                 "Button",
@@ -69,7 +72,6 @@
             Material3DemoCategory("Open on phone Dialog", OpenOnPhoneDialogDemos),
             ComposableDemo("Scaffold") { ScaffoldSample() },
             Material3DemoCategory("ScrollAway", ScrollAwayDemos),
-            ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
             ComposableDemo("Compact Button") { CompactButtonDemo() },
             ComposableDemo("Icon Button") { IconButtonDemo() },
             ComposableDemo("Image Button") { ImageButtonDemo() },
@@ -146,19 +148,25 @@
                 )
             ),
             Material3DemoCategory(
-                title = "Horizontal Page Indicator",
+                title = "Page Indicator",
                 listOf(
-                    ComposableDemo("Simple HorizontalPageIndicator") {
-                        HorizontalPageIndicatorSample()
-                    },
-                    ComposableDemo("HorizontalPageIndicator with Pager") {
+                    ComposableDemo("HorizontalPageIndicator") {
                         HorizontalPageIndicatorWithPagerSample(it.swipeToDismissBoxState)
                     },
+                    ComposableDemo("VerticalPageIndicator") {
+                        VerticalPageIndicatorWithPagerSample()
+                    },
                 )
             ),
             Material3DemoCategory(
                 title = "Swipe to Reveal",
                 listOf(
+                    ComposableDemo("Bi-directional / Non-anchoring") {
+                        Centralize { SwipeToRevealBothDirectionsNonAnchoring() }
+                    },
+                    ComposableDemo("Bi-directional Two Actions") {
+                        Centralize { SwipeToRevealBothDirections() }
+                    },
                     ComposableDemo("Two Actions") { Centralize { SwipeToRevealSample() } },
                     ComposableDemo("Two Undo Actions") {
                         Centralize { SwipeToRevealTwoActionsWithUndo() }
@@ -192,7 +200,15 @@
             ComposableDemo("Settings Demo") { SettingsDemo() },
             Material3DemoCategory(
                 title = "LazyColumn",
-                listOf(ComposableDemo("Notifications") { LazyColumnNotificationsDemo() })
+                listOf(
+                    ComposableDemo("Notifications") { LazyColumnNotificationsDemo() },
+                    ComposableDemo("Scaling Morphing Effect Sample") {
+                        LazyColumnScalingMorphingEffectSample()
+                    },
+                    ComposableDemo("Target Morphing Height Sample") {
+                        LazyColumnTargetMorphingHeightSample()
+                    }
+                )
             )
         )
     )
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
index 122d612..2aa006b 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
@@ -47,7 +47,7 @@
         Text("Confirm", Modifier.align(Alignment.Center))
         EdgeButton(
             onClick = { /* Do something */ },
-            buttonHeight = ButtonDefaults.EdgeButtonHeightMedium,
+            preferredHeight = ButtonDefaults.EdgeButtonHeightMedium,
             modifier = Modifier.align(Alignment.BottomEnd)
         ) {
             Icon(
@@ -68,7 +68,7 @@
         bottomButton = {
             EdgeButton(
                 onClick = {},
-                buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                 colors = buttonColors(containerColor = Color.DarkGray)
             ) {
                 Text("Ok", textAlign = TextAlign.Center)
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/HorizontalPageIndicatorSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/HorizontalPageIndicatorSample.kt
deleted file mode 100644
index c1752a1..0000000
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/HorizontalPageIndicatorSample.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.compose.material3.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.foundation.pager.rememberPagerState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.wear.compose.foundation.SwipeToDismissBoxState
-import androidx.wear.compose.foundation.edgeSwipeToDismiss
-import androidx.wear.compose.material3.HorizontalPageIndicator
-import androidx.wear.compose.material3.Slider
-import androidx.wear.compose.material3.Text
-
-@Sampled
-@Composable
-fun HorizontalPageIndicatorSample() {
-    val pageCount = 9
-    var selectedPage by remember { mutableStateOf(0) }
-
-    val animatedSelectedPage by
-        animateFloatAsState(
-            targetValue = selectedPage.toFloat(),
-            label = "animateSelectedPage",
-        )
-
-    Box(modifier = Modifier.fillMaxSize()) {
-        Slider(
-            modifier = Modifier.align(Alignment.Center),
-            value = selectedPage,
-            valueProgression = 0 until pageCount,
-            onValueChange = { selectedPage = it }
-        )
-        HorizontalPageIndicator(
-            pageCount = pageCount,
-            currentPage = selectedPage,
-            currentPageOffsetFraction = { animatedSelectedPage - selectedPage },
-        )
-    }
-}
-
-@Sampled
-@Composable
-fun HorizontalPageIndicatorWithPagerSample(swipeState: SwipeToDismissBoxState) {
-    val pageCount = 9
-    val pagerState = rememberPagerState { pageCount }
-
-    Box {
-        HorizontalPager(
-            modifier = Modifier.fillMaxSize().edgeSwipeToDismiss(swipeState),
-            state = pagerState,
-        ) { page ->
-            Box(modifier = Modifier.fillMaxSize()) {
-                Text(modifier = Modifier.align(Alignment.Center), text = "Page #$page")
-            }
-        }
-        HorizontalPageIndicator(
-            pageCount = pageCount,
-            currentPage = pagerState.currentPage,
-            currentPageOffsetFraction = { pagerState.currentPageOffsetFraction },
-        )
-    }
-}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PageIndicatorSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PageIndicatorSample.kt
new file mode 100644
index 0000000..018b762
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PageIndicatorSample.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.VerticalPager
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.foundation.SwipeToDismissBoxState
+import androidx.wear.compose.foundation.edgeSwipeToDismiss
+import androidx.wear.compose.foundation.pager.rememberPagerState
+import androidx.wear.compose.material3.HorizontalPageIndicator
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.VerticalPageIndicator
+
+@Sampled
+@Composable
+fun HorizontalPageIndicatorWithPagerSample(
+    swipeState: SwipeToDismissBoxState,
+) {
+    val pageCount = 9
+    val pagerState = rememberPagerState { pageCount }
+
+    Box {
+        HorizontalPager(
+            modifier = Modifier.fillMaxSize().edgeSwipeToDismiss(swipeState),
+            state = pagerState,
+        ) { page ->
+            Box(modifier = Modifier.fillMaxSize()) {
+                Text(modifier = Modifier.align(Alignment.Center), text = "Page #$page")
+            }
+        }
+        HorizontalPageIndicator(pagerState = pagerState)
+    }
+}
+
+@Sampled
+@Composable
+fun VerticalPageIndicatorWithPagerSample() {
+    val pageCount = 9
+    val pagerState = rememberPagerState { pageCount }
+
+    Box {
+        VerticalPager(
+            modifier = Modifier.fillMaxSize(),
+            state = pagerState,
+        ) { page ->
+            Box(modifier = Modifier.fillMaxSize()) {
+                Text(modifier = Modifier.align(Alignment.Center), text = "Page #$page")
+            }
+        }
+        VerticalPageIndicator(pagerState = pagerState)
+    }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
index 7e5ccbb..0bed18c 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
@@ -41,7 +41,9 @@
     SwipeToReveal(
         // Use the double action anchor width when revealing two actions
         revealState =
-            rememberRevealState(anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth),
+            rememberRevealState(
+                anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth,
+            ),
         actions = {
             primaryAction(
                 onClick = { /* This block is called when the primary action is executed. */ },
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
index 78cc200..d136db7 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
@@ -17,11 +17,8 @@
 package androidx.wear.compose.material3.samples
 
 import androidx.annotation.Sampled
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
@@ -32,7 +29,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.Text
@@ -59,16 +55,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
@@ -91,16 +85,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
@@ -123,16 +115,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
index 076610a..a987c30 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
@@ -19,6 +19,8 @@
 import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -37,6 +39,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
@@ -325,7 +328,7 @@
             expectedContentColor = MaterialTheme.colorScheme.onBackground
             expectedTextStyle = MaterialTheme.typography.titleMedium
             expectedTextAlign = TextAlign.Center
-            expectedTextMaxLines = AlertDialogDefaults.titleMaxLines
+            expectedTextMaxLines = AlertTitleMaxLines
             AlertDialog(
                 modifier = Modifier.testTag(TEST_TAG),
                 title = {
@@ -439,46 +442,87 @@
     @Test
     fun with_title_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val titleBottom = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot().bottom
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
-        confirmButtonTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.bottomSpacing)
+        confirmButtonTop.assertIsEqualTo(titleBottom + AlertBottomSpacing)
+    }
+
+    @Test
+    fun with_title_noBottomButton_positioning() {
+        rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+            ScreenConfiguration(SmallScreenSize) {
+                AlertDialog(
+                    show = true,
+                    title = { Text("Title") },
+                    onDismissRequest = {},
+                    bottomButton = null,
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(SmallScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item {
+                        Text(
+                            "ContentText",
+                            // We set height larger than the screen size to be sure that the list
+                            // will be scrollable
+                            modifier =
+                                Modifier.size(width = 100.dp, height = (SmallScreenSize + 50).dp)
+                                    .testTag(ContentTestTag)
+                        )
+                    }
+                }
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeUp() }
+
+        val contentBottom = rule.onNodeWithTag(ContentTestTag).getUnclippedBoundsInRoot().bottom
+        val alertDialogBottom = rule.onNodeWithTag(TEST_TAG).getUnclippedBoundsInRoot().bottom
+        // Assert that there is a proper padding between the bottom of the content and the bottom of
+        // the dialog.
+        contentBottom.assertIsEqualTo(
+            alertDialogBottom * (1 - AlertDialogDefaults.noEdgeButtonBottomPaddingFraction)
+        )
     }
 
     @Test
     fun with_icon_title_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val iconBottom = rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot().bottom
@@ -487,29 +531,31 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        confirmButtonTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        confirmButtonTop.assertIsEqualTo(titleBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_textMessage_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val iconBottom = rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot().bottom
@@ -520,31 +566,33 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        textTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(textBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        textTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(textBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_textMessage_content_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            ) {
-                item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+                }
             }
         }
 
@@ -558,31 +606,33 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        textTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        contentTop.assertIsEqualTo(textBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(contentBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        textTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        contentTop.assertIsEqualTo(textBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(contentBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_content_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            ) {
-                item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Box(modifier = Modifier.size(3.dp).testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+                }
             }
         }
 
@@ -594,9 +644,9 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        contentTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(contentBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        contentTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(contentBottom + AlertBottomSpacing)
     }
 
     // TODO: add more positioning tests for EdgeButton.
@@ -608,3 +658,5 @@
 private const val ContentTestTag = "content"
 private const val ConfirmButtonTestTag = "confirmButton"
 private const val DismissButtonTestTag = "dismissButton"
+private const val AlertScreenSize = 400
+private const val SmallScreenSize = 100
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
index 7d9b092..7eada56 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
@@ -135,7 +135,7 @@
             EdgeButton(
                 onClick = { /* Do something */ },
                 enabled = enabled,
-                buttonHeight = buttonHeight,
+                preferredHeight = buttonHeight,
                 modifier =
                     Modifier.align(Alignment.BottomEnd)
                         .testTag(TEST_TAG)
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt
deleted file mode 100644
index 92820fd..0000000
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.compose.material3
-
-import android.os.Build
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.testutils.assertAgainstGolden
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.DeviceConfigurationOverride
-import androidx.compose.ui.test.LayoutDirection
-import androidx.compose.ui.test.RoundScreen
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import androidx.test.screenshot.AndroidXScreenshotTestRule
-import androidx.wear.compose.material3.HorizontalPageIndicatorTest.Companion.PAGE_COUNT
-import androidx.wear.compose.material3.HorizontalPageIndicatorTest.Companion.SELECTED_PAGE_INDEX
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-class HorizontalPageIndicatorScreenshotTest {
-
-    @get:Rule val rule = createComposeRule()
-
-    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
-
-    @get:Rule val testName = TestName()
-
-    @Test
-    fun horizontalPageIndicator_circular_selected_page() {
-        selected_page(true, LayoutDirection.Ltr)
-    }
-
-    @Test
-    fun horizontalPageIndicator_linear_selected_page() {
-        selected_page(false, LayoutDirection.Ltr)
-    }
-
-    @Test
-    fun horizontalPageIndicator_circular_selected_page_rtl() {
-        selected_page(true, LayoutDirection.Rtl)
-    }
-
-    @Test
-    fun horizontalPageIndicator_linear_selected_page_rtl() {
-        selected_page(false, LayoutDirection.Rtl)
-    }
-
-    @Test
-    fun horizontalPageIndicator_circular_between_pages() {
-        between_pages(true)
-    }
-
-    @Test
-    fun horizontalPageIndicator_linear_between_pages() {
-        between_pages(false)
-    }
-
-    private fun selected_page(isRound: Boolean, layoutDirection: LayoutDirection) {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(
-                DeviceConfigurationOverride.LayoutDirection(layoutDirection)
-            ) {
-                defaultHorizontalPageIndicator(isRound)
-            }
-        }
-        rule.waitForIdle()
-
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, testName.methodName)
-    }
-
-    private fun between_pages(isRound: Boolean) {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
-                Box(modifier = Modifier.testTag(TEST_TAG).size(200.dp)) {
-                    HorizontalPageIndicator(
-                        pageCount = PAGE_COUNT,
-                        currentPage = SELECTED_PAGE_INDEX,
-                        currentPageOffsetFraction = { 0.5f },
-                        selectedColor = Color.Yellow,
-                        unselectedColor = Color.Red,
-                        indicatorSize = 15.dp
-                    )
-                }
-            }
-        }
-        rule.waitForIdle()
-
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, testName.methodName)
-    }
-
-    @Composable
-    private fun defaultHorizontalPageIndicator(isRound: Boolean) {
-        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
-            Box(modifier = Modifier.testTag(TEST_TAG).size(200.dp)) {
-                HorizontalPageIndicator(
-                    pageCount = PAGE_COUNT,
-                    currentPage = SELECTED_PAGE_INDEX,
-                    currentPageOffsetFraction = { 0.0f },
-                    selectedColor = Color.Yellow,
-                    unselectedColor = Color.Red,
-                    indicatorSize = 15.dp
-                )
-            }
-        }
-    }
-}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt
deleted file mode 100644
index 92182e4..0000000
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.DeviceConfigurationOverride
-import androidx.compose.ui.test.RoundScreen
-import androidx.compose.ui.test.assertHeightIsEqualTo
-import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.dp
-import org.junit.Rule
-import org.junit.Test
-
-@RequiresApi(Build.VERSION_CODES.O)
-class HorizontalPageIndicatorTest {
-    @get:Rule val rule = createComposeRule()
-
-    @Test
-    public fun supports_testtag_circular() {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(
-                DeviceConfigurationOverride.RoundScreen(isScreenRound = true)
-            ) {
-                HorizontalPageIndicator(
-                    modifier = Modifier.testTag(TEST_TAG),
-                    pageCount = PAGE_COUNT,
-                    currentPage = SELECTED_PAGE_INDEX,
-                    currentPageOffsetFraction = { 0.0f },
-                )
-            }
-        }
-        rule.onNodeWithTag(TEST_TAG).assertExists()
-    }
-
-    @Test
-    public fun supports_testtag_linear() {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(
-                DeviceConfigurationOverride.RoundScreen(isScreenRound = false)
-            ) {
-                HorizontalPageIndicator(
-                    modifier = Modifier.testTag(TEST_TAG),
-                    pageCount = PAGE_COUNT,
-                    currentPage = SELECTED_PAGE_INDEX,
-                    currentPageOffsetFraction = { 0.0f },
-                )
-            }
-        }
-        rule.onNodeWithTag(TEST_TAG).assertExists()
-    }
-
-    @Test
-    public fun position_is_selected_circular() {
-        position_is_selected(isRound = true)
-    }
-
-    @Test
-    public fun position_is_selected_linear() {
-        position_is_selected(isRound = false)
-    }
-
-    @Test
-    public fun in_between_positions_circular() {
-        in_between_positions(isRound = true)
-    }
-
-    @Test
-    public fun in_between_positions_linear() {
-        in_between_positions(isRound = false)
-    }
-
-    @Test
-    fun horizontal_page_indicator_circular_9_pages_sized_appropriately() {
-        val indicatorSize = 6.dp
-        val spacing = 2.dp
-
-        rule.setContent {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                Box(modifier = Modifier.size(200.dp)) {
-                    HorizontalPageIndicator(
-                        modifier = Modifier.testTag(TEST_TAG),
-                        pageCount = 9,
-                        currentPage = 1,
-                        currentPageOffsetFraction = { 0f },
-                        indicatorSize = indicatorSize,
-                        spacing = spacing
-                    )
-                }
-            }
-        }
-
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .assertWidthIsEqualTo(
-                (indicatorSize + spacing) * 6 + PageIndicatorDefaults.edgePadding * 2
-            )
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .assertHeightIsEqualTo(indicatorSize * 2 + PageIndicatorDefaults.edgePadding * 2)
-    }
-
-    @Test
-    fun horizontal_page_indicator_circular_3_pages_sized_appropriately() {
-        val indicatorSize = 6.dp
-        val spacing = 2.dp
-        val pagesCount = 3
-
-        rule.setContent {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                Box(modifier = Modifier.size(200.dp)) {
-                    HorizontalPageIndicator(
-                        modifier = Modifier.testTag(TEST_TAG),
-                        pageCount = pagesCount,
-                        currentPage = 1,
-                        currentPageOffsetFraction = { 0f },
-                        indicatorSize = indicatorSize,
-                        spacing = spacing
-                    )
-                }
-            }
-        }
-
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .assertWidthIsEqualTo(
-                (indicatorSize + spacing) * pagesCount + PageIndicatorDefaults.edgePadding * 2
-            )
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .assertHeightIsEqualTo(indicatorSize * 2 + PageIndicatorDefaults.edgePadding * 2)
-    }
-
-    private fun position_is_selected(isRound: Boolean) {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
-                Box(modifier = Modifier.testTag(TEST_TAG).size(150.dp)) {
-                    HorizontalPageIndicator(
-                        pageCount = PAGE_COUNT,
-                        currentPage = SELECTED_PAGE_INDEX,
-                        currentPageOffsetFraction = { 0.0f },
-                        selectedColor = selectedColor,
-                        unselectedColor = unselectedColor,
-                        indicatorSize = 20.dp
-                    )
-                }
-            }
-        }
-        rule.waitForIdle()
-
-        // A selected dot with specified color should be visible on the screen, which is apprx 1.3%
-        // (1.3% per dot, 1 dot in total)
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertColorInPercentageRange(selectedColor, 1.2f..1.6f)
-        // Unselected dots should also be visible on the screen, and should take around 4%
-        // (1.3% per dot, 3 dots total)
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertColorInPercentageRange(unselectedColor, 3.8f..4.5f)
-    }
-
-    private fun in_between_positions(isRound: Boolean) {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
-                Box(modifier = Modifier.testTag(TEST_TAG).size(150.dp)) {
-                    HorizontalPageIndicator(
-                        pageCount = PAGE_COUNT,
-                        currentPage = SELECTED_PAGE_INDEX,
-                        currentPageOffsetFraction = { 0.5f },
-                        selectedColor = selectedColor,
-                        unselectedColor = unselectedColor,
-                        indicatorSize = 20.dp
-                    )
-                }
-            }
-        }
-        rule.waitForIdle()
-
-        // Selected color should occupy 2 dots with space in between, which
-        // approximately equals to 3.5%
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertColorInPercentageRange(selectedColor, 3f..4f)
-        // Unselected dots ( which doesn't participate in color merge)
-        // should also be visible on the screen, and should take around 2.7%
-        // (1.3% per dot, 2 dots in total)
-        rule
-            .onNodeWithTag(TEST_TAG)
-            .captureToImage()
-            .assertColorInPercentageRange(unselectedColor, 2.5f..3f)
-    }
-
-    companion object {
-        val selectedColor = Color.Yellow
-        val unselectedColor = Color.Red
-
-        const val PAGE_COUNT = 4
-        const val SELECTED_PAGE_INDEX = 1
-    }
-}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index e3af5a4..79a7733 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -123,9 +123,10 @@
 
 /**
  * Valid characters for golden identifiers are [A-Za-z0-9_-] TestParameterInjector adds '[' +
- * parameter_values + ']' to the test name.
+ * parameter_values + ']' + ',' to the test name.
  */
-fun TestName.goldenIdentifier(): String = methodName.replace("[", "_").replace("]", "")
+fun TestName.goldenIdentifier(): String =
+    methodName.replace("[", "_").replace("]", "").replace(",", "_")
 
 internal const val TEST_TAG = "test-item"
 
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorScreenshotTest.kt
new file mode 100644
index 0000000..c00c2e5
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorScreenshotTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.LayoutDirection
+import androidx.compose.ui.test.RoundScreen
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.foundation.pager.PagerState
+import androidx.wear.compose.material3.PageIndicatorTest.Companion.PAGE_COUNT
+import androidx.wear.compose.material3.PageIndicatorTest.Companion.SELECTED_PAGE_INDEX
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(TestParameterInjector::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class PageIndicatorScreenshotTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule val testName = TestName()
+
+    @Test
+    fun horizontalPageIndicator_selected_page(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(shape.isRound, true, LayoutDirection.Ltr, screenSize)
+    }
+
+    @Test
+    fun verticalPageIndicator_selected_page(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(shape.isRound, false, LayoutDirection.Ltr, screenSize)
+    }
+
+    @Test
+    fun horizontalPageIndicator_selected_page_rtl(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(shape.isRound, true, LayoutDirection.Rtl, screenSize)
+    }
+
+    @Test
+    fun verticalPageIndicator_selected_page_rtl(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(shape.isRound, false, LayoutDirection.Rtl, screenSize)
+    }
+
+    @Test
+    fun horizontalPageIndicator_between_pages(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(
+            shape.isRound,
+            true,
+            LayoutDirection.Ltr,
+            screenSize,
+            offsetFraction = 0.5f,
+        )
+    }
+
+    @Test
+    fun verticalPageIndicator_between_pages(
+        @TestParameter screenSize: ScreenSize,
+        @TestParameter shape: ScreenShape
+    ) {
+        verifyPageIndicator(
+            shape.isRound,
+            false,
+            LayoutDirection.Ltr,
+            screenSize,
+            offsetFraction = 0.5f
+        )
+    }
+
+    @Test
+    fun horizontalPageIndicator_circular_9_pages(@TestParameter screenSize: ScreenSize) {
+        verifyPageIndicator(
+            true,
+            true,
+            LayoutDirection.Ltr,
+            screenSize,
+            pageCount = 9,
+            selectedPageIndex = 6
+        )
+    }
+
+    @Test
+    fun verticalPageIndicator_circular_9_pages(@TestParameter screenSize: ScreenSize) {
+        verifyPageIndicator(
+            true,
+            false,
+            LayoutDirection.Ltr,
+            screenSize,
+            pageCount = 9,
+            selectedPageIndex = 6
+        )
+    }
+
+    // TODO(b/369535289) Add tests for linear page indicator with 9 pages
+
+    private fun verifyPageIndicator(
+        isRound: Boolean,
+        isHorizontal: Boolean,
+        layoutDirection: LayoutDirection,
+        screenSize: ScreenSize = ScreenSize.SMALL,
+        offsetFraction: Float = 0.0f,
+        pageCount: Int = PAGE_COUNT,
+        selectedPageIndex: Int = SELECTED_PAGE_INDEX
+    ) {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.LayoutDirection(layoutDirection)
+            ) {
+                PageIndicator(
+                    isRound,
+                    isHorizontal,
+                    offsetFraction,
+                    screenSize,
+                    pageCount,
+                    selectedPageIndex
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, testName.goldenIdentifier())
+    }
+
+    @Composable
+    private fun PageIndicator(
+        isRound: Boolean,
+        isHorizontal: Boolean,
+        offsetFraction: Float,
+        screenSize: ScreenSize,
+        pageCount: Int,
+        selectedPageIndex: Int
+    ) {
+        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
+            ScreenConfiguration(screenSize.size) {
+                Box(modifier = Modifier.testTag(TEST_TAG).fillMaxSize().background(Color.White)) {
+                    val pagerState =
+                        PagerState(
+                            currentPage = selectedPageIndex,
+                            currentPageOffsetFraction = offsetFraction,
+                            pageCount = { pageCount }
+                        )
+                    if (isHorizontal) {
+                        HorizontalPageIndicator(pagerState = pagerState)
+                    } else {
+                        VerticalPageIndicator(pagerState = pagerState)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt
new file mode 100644
index 0000000..34be1f5
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt
@@ -0,0 +1,349 @@
+/*
+ * 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.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.LayoutDirection
+import androidx.compose.ui.test.RoundScreen
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.then
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.pager.PagerState
+import org.junit.Rule
+import org.junit.Test
+
+@RequiresApi(Build.VERSION_CODES.O)
+class PageIndicatorTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    public fun horizontalPageIndicator_supports_testtag_circular() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = true)
+            ) {
+                HorizontalPageIndicator(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    pagerState = pagerState_start,
+                )
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    public fun horizontalPageIndicator_supports_testtag_linear() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = false)
+            ) {
+                HorizontalPageIndicator(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    pagerState = pagerState_start
+                )
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    public fun verticalPageIndicator_supports_testtag_circular() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = true)
+            ) {
+                VerticalPageIndicator(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    pagerState = pagerState_start,
+                )
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    public fun verticalPageIndicator_supports_testtag_linear() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = false)
+            ) {
+                VerticalPageIndicator(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    pagerState = pagerState_start
+                )
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    public fun horizontalPageIndicator_position_is_selected_circular() {
+        horizontalPageIndicator_position_is_selected_circular(LayoutDirection.Ltr)
+    }
+
+    @Test
+    public fun horizontalPageIndicator_in_between_positions_circular() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    HorizontalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState = pagerState_middle,
+                        selectedColor = selectedColor,
+                        unselectedColor = unselectedColor,
+                        backgroundColor = backgroundColor
+                    )
+                }
+            }
+        }
+        rule.waitForIdle()
+
+        // Selected color should occupy 2 dots with space in between, which
+        // approximately equals to 12%
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(selectedColor, 11f..14f)
+        // Unselected dots should also be visible on the screen, and should take around 9%
+        // (4.4% per dot, 2 dots total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(unselectedColor, 7f..10f)
+
+        // Check that background color exists
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(backgroundColor)
+    }
+
+    @Test
+    public fun verticalPageIndicator_position_is_selected_circular() {
+        verticalPageIndicator_position_is_selected_circular(LayoutDirection.Ltr)
+    }
+
+    @Test
+    public fun verticalPageIndicator_in_between_positions_circular() {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    VerticalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState = pagerState_middle,
+                        selectedColor = selectedColor,
+                        unselectedColor = unselectedColor,
+                        backgroundColor = backgroundColor
+                    )
+                }
+            }
+        }
+        rule.waitForIdle()
+
+        // Selected color should occupy 2 dots with space in between, which
+        // approximately equals to 12%
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(selectedColor, 10f..14f)
+        // Unselected dots ( which doesn't participate in color merge)
+        // should also be visible on the screen, and should take around 8.8%
+        // (4.4% per dot, 2 dots in total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(unselectedColor, 7.5f..9f)
+
+        // Check that background color exists
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(backgroundColor)
+    }
+
+    @Test
+    fun horizontalPageIndicator_9_pages_sized_appropriately_circular() {
+        val indicatorSize = PageIndicatorItemSize
+        val spacing = PageIndicatorSpacing
+        val padding = PaddingDefaults.edgePadding
+        rule.setContent {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    HorizontalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState =
+                            PagerState(
+                                currentPage = 1,
+                                currentPageOffsetFraction = 0.0f,
+                                pageCount = { 9 }
+                            ),
+                    )
+                }
+            }
+        }
+
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .assertWidthIsEqualTo((indicatorSize + spacing) * 6 + padding * 2)
+        rule.onNodeWithTag(TEST_TAG).assertHeightIsEqualTo(indicatorSize * 2 + padding * 2)
+    }
+
+    @Test
+    fun horizontalPageIndicator_3_pages_sized_appropriately_circular() {
+        val indicatorSize = PageIndicatorItemSize
+        val spacing = PageIndicatorSpacing
+        val pagesCount = 3
+        val padding = PaddingDefaults.edgePadding
+
+        rule.setContent {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    HorizontalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState =
+                            PagerState(
+                                currentPage = 1,
+                                currentPageOffsetFraction = 0.0f,
+                                pageCount = { pagesCount }
+                            ),
+                    )
+                }
+            }
+        }
+
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .assertWidthIsEqualTo((indicatorSize + spacing) * pagesCount + padding * 2)
+        rule.onNodeWithTag(TEST_TAG).assertHeightIsEqualTo(indicatorSize * 2 + padding * 2)
+    }
+
+    @Test
+    fun horizontalPageIndicator_position_is_selected_circular_rtl() {
+        horizontalPageIndicator_position_is_selected_circular(LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun verticalPageIndicator_position_is_selected_circular_rtl() {
+        verticalPageIndicator_position_is_selected_circular(LayoutDirection.Rtl)
+    }
+
+    private fun horizontalPageIndicator_position_is_selected_circular(
+        layoutDirection: LayoutDirection
+    ) {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(true)
+                    .then(DeviceConfigurationOverride.LayoutDirection(layoutDirection))
+            ) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    HorizontalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState = pagerState_start,
+                        selectedColor = selectedColor,
+                        unselectedColor = unselectedColor,
+                        backgroundColor = backgroundColor
+                    )
+                }
+            }
+        }
+        rule.waitForIdle()
+
+        // A selected dot with specified color should be visible on the screen, which is apprx 4.4%
+        // (4.4% per dot, 1 dot in total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(selectedColor, 4f..5f)
+        // Unselected dots should also be visible on the screen, and should take around 13.2%
+        // (4.4% per dot, 3 dots total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(unselectedColor, 11f..16f)
+
+        // Check that background color exists
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(backgroundColor)
+    }
+
+    private fun verticalPageIndicator_position_is_selected_circular(
+        layoutDirection: LayoutDirection
+    ) {
+        rule.setContentWithTheme {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(true)
+                    .then(DeviceConfigurationOverride.LayoutDirection(layoutDirection))
+            ) {
+                Box(modifier = Modifier.size(150.dp)) {
+                    VerticalPageIndicator(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        pagerState = pagerState_start,
+                        selectedColor = selectedColor,
+                        unselectedColor = unselectedColor,
+                        backgroundColor = backgroundColor
+                    )
+                }
+            }
+        }
+        rule.waitForIdle()
+
+        // A selected dot with specified color should be visible on the screen, which is apprx 4.4%
+        // (4.% per dot, 1 dot in total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(selectedColor, 3.5f..5.5f)
+        // Unselected dots should also be visible on the screen, and should take around 13%
+        // (4.4% per dot, 3 dots total)
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertColorInPercentageRange(unselectedColor, 11f..14f)
+
+        // Check that background color exists
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(backgroundColor)
+    }
+
+    private val pagerState_start =
+        PagerState(
+            currentPage = SELECTED_PAGE_INDEX,
+            currentPageOffsetFraction = 0.0f,
+            pageCount = { PAGE_COUNT }
+        )
+
+    private val pagerState_middle =
+        PagerState(
+            currentPage = SELECTED_PAGE_INDEX,
+            currentPageOffsetFraction = 0.5f,
+            pageCount = { PAGE_COUNT }
+        )
+
+    companion object {
+        val selectedColor = Color.Yellow
+        val unselectedColor = Color.Red
+        val backgroundColor = Color.Green
+
+        const val PAGE_COUNT = 4
+        const val SELECTED_PAGE_INDEX = 1
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
index 2031ed4..ca94d41 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
@@ -77,7 +77,7 @@
     fun contains_progress_color() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 1f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -100,7 +100,7 @@
     fun contains_progress_incomplete_color() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -123,7 +123,7 @@
     fun change_start_end_angle() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 startAngle = 0f,
                 endAngle = 180f,
@@ -152,7 +152,7 @@
     fun set_small_progress_value() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.02f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -178,7 +178,7 @@
     fun set_small_stroke_width() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 strokeWidth = CircularProgressIndicatorDefaults.smallStrokeWidth,
                 colors =
@@ -204,7 +204,7 @@
     fun set_large_stroke_width() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 strokeWidth = CircularProgressIndicatorDefaults.largeStrokeWidth,
                 colors =
@@ -231,7 +231,7 @@
     fun progress_disabled_contains_disabled_colors() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 enabled = false,
                 colors =
@@ -253,6 +253,10 @@
 
     private fun setContentWithTheme(composable: @Composable BoxScope.() -> Unit) {
         // Use constant size modifier to limit relative color percentage ranges.
-        rule.setContentWithTheme(modifier = Modifier.size(204.dp), composable = composable)
+        rule.setContentWithTheme(modifier = Modifier.size(COMPONENT_SIZE)) {
+            ScreenConfiguration(SCREEN_SIZE_LARGE) { composable() }
+        }
     }
 }
+
+private val COMPONENT_SIZE = 204.dp
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwipeToRevealScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwipeToRevealScreenshotTest.kt
index 5c2711f..d0455e0 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwipeToRevealScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwipeToRevealScreenshotTest.kt
@@ -33,6 +33,7 @@
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
 import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.SwipeDirection
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
 import org.junit.Rule
@@ -57,7 +58,7 @@
             Box(modifier = Modifier.fillMaxSize()) {
                 SwipeToReveal(
                     modifier = Modifier.testTag(TEST_TAG),
-                    revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                    revealState = rememberRevealState(initialValue = RevealValue.RightRevealing),
                     actions = {
                         primaryAction(
                             {},
@@ -83,7 +84,7 @@
                     modifier = Modifier.testTag(TEST_TAG),
                     revealState =
                         rememberRevealState(
-                            initialValue = RevealValue.Revealing,
+                            initialValue = RevealValue.RightRevealing,
                             anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth
                         ),
                     actions = {
@@ -114,7 +115,7 @@
             Box(modifier = Modifier.fillMaxSize()) {
                 SwipeToReveal(
                     modifier = Modifier.testTag(TEST_TAG),
-                    revealState = rememberRevealState(initialValue = RevealValue.Revealed),
+                    revealState = rememberRevealState(initialValue = RevealValue.RightRevealed),
                     actions = {
                         primaryAction({}, /* Empty for testing */ {}, /* Empty for testing */ "")
                         undoPrimaryAction({}, "Undo Primary")
@@ -134,7 +135,7 @@
                 SwipeToReveal(
                     modifier = Modifier.testTag(TEST_TAG),
                     revealState =
-                        rememberRevealState(initialValue = RevealValue.Revealed).apply {
+                        rememberRevealState(initialValue = RevealValue.RightRevealed).apply {
                             lastActionType = RevealActionType.SecondaryAction
                         },
                     actions = {
@@ -176,7 +177,7 @@
             Box(modifier = Modifier.fillMaxSize()) {
                 SwipeToReveal(
                     modifier = Modifier.testTag(TEST_TAG),
-                    revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                    revealState = rememberRevealState(initialValue = RevealValue.RightRevealing),
                     actionButtonHeight = SwipeToRevealDefaults.LargeActionButtonHeight,
                     actions = {
                         primaryAction(
@@ -205,7 +206,7 @@
                     modifier = Modifier.testTag(TEST_TAG),
                     revealState =
                         rememberRevealState(
-                            initialValue = RevealValue.Revealing,
+                            initialValue = RevealValue.RightRevealing,
                             anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth
                         ),
                     actionButtonHeight = SwipeToRevealDefaults.LargeActionButtonHeight,
@@ -230,6 +231,40 @@
         }
     }
 
+    @OptIn(ExperimentalWearFoundationApi::class)
+    @Test
+    fun swipeToReveal_showsPrimaryAndSecondaryActionsLeft(@TestParameter screenSize: ScreenSize) {
+        verifyScreenshotForSize(screenSize) {
+            Box(modifier = Modifier.fillMaxSize()) {
+                SwipeToReveal(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    revealState =
+                        rememberRevealState(
+                            initialValue = RevealValue.LeftRevealing,
+                            anchorWidth = SwipeToRevealDefaults.DoubleActionAnchorWidth,
+                            swipeDirection = SwipeDirection.Both
+                        ),
+                    actions = {
+                        primaryAction(
+                            {},
+                            { Icon(Icons.Outlined.Close, contentDescription = "Clear") },
+                            "Clear"
+                        )
+                        secondaryAction(
+                            {},
+                            { Icon(Icons.Outlined.MoreVert, contentDescription = "More") },
+                            "More"
+                        )
+                    }
+                ) {
+                    Button({}, Modifier.fillMaxWidth()) {
+                        Text("This text should be partially visible.")
+                    }
+                }
+            }
+        }
+    }
+
     private fun verifyScreenshotForSize(screenSize: ScreenSize, content: @Composable () -> Unit) {
         rule.verifyScreenshot(
             screenshotRule = screenshotRule,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
index 4f7238f..5fd2645 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
@@ -39,7 +39,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
@@ -47,13 +46,10 @@
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
 import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.material3.AlertDialogDefaults.bottomSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.contentTopSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.iconBottomSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.textMessageTopSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.textPaddingFraction
-import androidx.wear.compose.material3.AlertDialogDefaults.titlePaddingFraction
+import androidx.wear.compose.material3.PaddingDefaults.horizontalContentPadding
+import androidx.wear.compose.material3.PaddingDefaults.verticalContentPadding
 import androidx.wear.compose.materialcore.isSmallScreen
+import androidx.wear.compose.materialcore.screenHeightDp
 import androidx.wear.compose.materialcore.screenWidthDp
 
 /**
@@ -72,14 +68,14 @@
  * @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping
  *   right (typically also called by the [dismissButton]).
  * @param confirmButton A slot for a [Button] indicating positive sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.ConfirmButton] in this slot with onClick callback.
+ *   must remove the dialog from the composition hierarchy e.g. by setting [show] to false. It's
+ *   recommended to use [AlertDialogDefaults.ConfirmButton] in this slot with onClick callback.
  * @param title A slot for displaying the title of the dialog. Title should contain a summary of the
  *   dialog's purpose or content and should not exceed 3 lines of text.
  * @param modifier Modifier to be applied to the dialog content.
  * @param dismissButton A slot for a [Button] indicating negative sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.DismissButton] in this slot with onClick callback.
+ *   must remove the dialog from the composition hierarchy e.g. by setting [show] to false. It's
+ *   recommended to use [AlertDialogDefaults.DismissButton] in this slot with onClick callback.
  * @param icon Optional slot for an icon to be shown at the top of the dialog.
  * @param text Optional slot for displaying the message of the dialog below the title. Should
  *   contain additional text that presents further details about the dialog's purpose if the title
@@ -104,7 +100,7 @@
     icon: @Composable (() -> Unit)? = null,
     text: @Composable (() -> Unit)? = null,
     verticalArrangement: Arrangement.Vertical = AlertDialogDefaults.VerticalArrangement,
-    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(hasBottomButton = false),
+    contentPadding: PaddingValues = AlertDialogDefaults.confirmDismissContentPadding(),
     properties: DialogProperties = DialogProperties(),
     content: (ScalingLazyListScope.() -> Unit)? = null
 ) {
@@ -142,9 +138,9 @@
  * @param show A boolean indicating whether the dialog should be displayed.
  * @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping to
  *   the right or by other dismiss action.
- * @param bottomButton A slot for a [EdgeButton] indicating positive sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.BottomButton] in this slot with onClick callback.
+ * @param bottomButton Optional slot for a [EdgeButton] indicating positive sentiment. Clicking the
+ *   button must remove the dialog from the composition hierarchy e.g. by setting [show] to false.
+ *   It's recommended to use [AlertDialogDefaults.BottomButton] in this slot with onClick callback.
  * @param title A slot for displaying the title of the dialog. Title should contain a summary of the
  *   dialog's purpose or content and should not exceed 3 lines of text.
  * @param modifier Modifier to be applied to the dialog content.
@@ -163,13 +159,13 @@
 fun AlertDialog(
     show: Boolean,
     onDismissRequest: () -> Unit,
-    bottomButton: @Composable BoxScope.() -> Unit,
+    bottomButton: (@Composable BoxScope.() -> Unit)?,
     title: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     icon: @Composable (() -> Unit)? = null,
     text: @Composable (() -> Unit)? = null,
     verticalArrangement: Arrangement.Vertical = AlertDialogDefaults.VerticalArrangement,
-    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(hasBottomButton = true),
+    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(bottomButton != null),
     properties: DialogProperties = DialogProperties(),
     content: (ScalingLazyListScope.() -> Unit)? = null
 ) {
@@ -183,7 +179,9 @@
         title = title,
         icon = icon,
         text = text,
-        alertButtonsParams = AlertButtonsParams.BottomButton(bottomButton),
+        alertButtonsParams =
+            if (bottomButton != null) AlertButtonsParams.BottomButton(bottomButton)
+            else AlertButtonsParams.NoButtons,
         content = content
     )
 }
@@ -203,7 +201,7 @@
         EdgeButton(
             modifier = Modifier.padding(top = edgeButtonExtraTopPadding),
             onClick = onClick,
-            buttonHeight = ButtonDefaults.EdgeButtonHeightMedium,
+            preferredHeight = ButtonDefaults.EdgeButtonHeightMedium,
             content = content
         )
     }
@@ -258,23 +256,32 @@
     }
 
     /**
-     * The padding to apply around the content. Changes based on whether the dialog has a bottom
-     * button or not.
-     *
-     * @param hasBottomButton A boolean indicating whether the dialog has a bottom button.
+     * The padding to apply around the content for the [AlertDialog] variation with confirm dismiss
+     * buttons.
+     */
+    @Composable
+    fun confirmDismissContentPadding(): PaddingValues {
+        val verticalPadding = verticalContentPadding()
+        val horizontalPadding = horizontalContentPadding()
+        return PaddingValues(horizontal = horizontalPadding, vertical = verticalPadding)
+    }
+
+    /**
+     * The padding to apply around the content for the [AlertDialog] variation with a stack of
+     * options and optional bottom button.
      */
     @Composable
     fun contentPadding(hasBottomButton: Boolean): PaddingValues {
-        val screenWidth = LocalConfiguration.current.screenWidthDp
-        val verticalContentPadding =
-            screenWidth.dp * PaddingDefaults.verticalContentPaddingPercentage / 100
-        val horizontalContentPadding =
-            screenWidth.dp * PaddingDefaults.horizontalContentPaddingPercentage / 100
+        val topPadding = verticalContentPadding()
+        val horizontalPadding = horizontalContentPadding()
+        val bottomPadding =
+            if (hasBottomButton) edgeButtonHeightWithPadding
+            else screenHeightDp().dp * noEdgeButtonBottomPaddingFraction
         return PaddingValues(
-            top = verticalContentPadding,
-            bottom = if (hasBottomButton) edgeButtonHeightWithPadding else verticalContentPadding,
-            start = horizontalContentPadding,
-            end = horizontalContentPadding,
+            top = topPadding,
+            bottom = bottomPadding,
+            start = horizontalPadding,
+            end = horizontalPadding,
         )
     }
 
@@ -310,19 +317,10 @@
     }
 
     /** The extra top padding to apply to the edge button. */
-    val edgeButtonExtraTopPadding = 1.dp
-
-    internal val edgeButtonHeightWithPadding = ButtonDefaults.EdgeButtonHeightMedium + 7.dp
-
-    internal val titlePaddingFraction = 0.12f
-    internal val textPaddingFraction = 0.0416f
-
+    private val edgeButtonExtraTopPadding = 1.dp
+    private val edgeButtonHeightWithPadding = ButtonDefaults.EdgeButtonHeightMedium + 7.dp
+    internal val noEdgeButtonBottomPaddingFraction = 0.3646f
     internal val cancelButtonPadding = 1.dp
-    internal val iconBottomSpacing = 4.dp
-    internal val textMessageTopSpacing = 8.dp
-    internal val contentTopSpacing = 8.dp
-    internal val bottomSpacing = 8.dp
-    internal val titleMaxLines = 3
 }
 
 @Composable
@@ -370,7 +368,7 @@
                     item { TextMessage(text) }
                 }
                 if (content != null) {
-                    item { Spacer(Modifier.height(contentTopSpacing)) }
+                    item { Spacer(Modifier.height(ContentTopSpacing)) }
                     content()
                 }
 
@@ -379,8 +377,9 @@
                         item { ConfirmDismissButtons(alertButtonsParams) }
                     is AlertButtonsParams.BottomButton ->
                         if (content == null) {
-                            item { Spacer(Modifier.height(bottomSpacing)) }
+                            item { Spacer(Modifier.height(AlertBottomSpacing)) }
                         }
+                    is AlertButtonsParams.NoButtons -> Unit
                 }
             }
         }
@@ -391,13 +390,13 @@
 private fun IconAlert(content: @Composable () -> Unit) {
     Column {
         content()
-        Spacer(Modifier.height(iconBottomSpacing))
+        Spacer(Modifier.height(AlertIconBottomSpacing))
     }
 }
 
 @Composable
 private fun Title(content: @Composable () -> Unit) {
-    val horizontalPadding = screenWidthDp().dp * titlePaddingFraction
+    val horizontalPadding = screenWidthDp().dp * TitlePaddingFraction
     Column(modifier = Modifier.padding(horizontal = horizontalPadding)) {
         CompositionLocalProvider(
             LocalContentColor provides MaterialTheme.colorScheme.onBackground,
@@ -405,7 +404,7 @@
             LocalTextConfiguration provides
                 TextConfiguration(
                     textAlign = TextAlign.Center,
-                    maxLines = AlertDialogDefaults.titleMaxLines,
+                    maxLines = AlertTitleMaxLines,
                     overflow = TextOverflow.Ellipsis
                 ),
             content = content
@@ -416,7 +415,7 @@
 @Composable
 private fun ConfirmDismissButtons(alertButtonsParams: AlertButtonsParams.ConfirmDismissButtons) {
     Column {
-        Spacer(modifier = Modifier.height(bottomSpacing))
+        Spacer(modifier = Modifier.height(AlertBottomSpacing))
         Row(
             horizontalArrangement = Arrangement.Center,
             verticalAlignment = Alignment.CenterVertically
@@ -432,9 +431,9 @@
 
 @Composable
 private fun TextMessage(content: @Composable () -> Unit) {
-    val horizontalPadding = screenWidthDp().dp * textPaddingFraction
+    val horizontalPadding = screenWidthDp().dp * TextPaddingFraction
     Column(modifier = Modifier.padding(horizontal = horizontalPadding)) {
-        Spacer(Modifier.height(textMessageTopSpacing))
+        Spacer(Modifier.height(AlertTextMessageTopSpacing))
         CompositionLocalProvider(
             LocalContentColor provides MaterialTheme.colorScheme.onBackground,
             LocalTextStyle provides MaterialTheme.typography.bodyMedium,
@@ -450,12 +449,23 @@
 }
 
 private sealed interface AlertButtonsParams {
-    data class BottomButton(
+    object NoButtons : AlertButtonsParams
+
+    class BottomButton(
         val bottomButton: @Composable BoxScope.() -> Unit,
     ) : AlertButtonsParams
 
-    data class ConfirmDismissButtons(
+    class ConfirmDismissButtons(
         val confirmButton: @Composable RowScope.() -> Unit,
         val dismissButton: @Composable RowScope.() -> Unit
     ) : AlertButtonsParams
 }
+
+internal val AlertIconBottomSpacing = 4.dp
+internal val AlertTextMessageTopSpacing = 8.dp
+internal val AlertBottomSpacing = 8.dp
+internal const val AlertTitleMaxLines = 3
+
+private val ContentTopSpacing = 8.dp
+private const val TextPaddingFraction = 0.0416f
+private const val TitlePaddingFraction = 0.12f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
index 7764d08..b5e9a6c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
@@ -34,9 +34,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
-import androidx.compose.material.icons.filled.Check
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
@@ -66,6 +63,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.ButtonDefaults.buttonColors
 import androidx.wear.compose.material3.ButtonDefaults.filledTonalButtonColors
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerDay
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerMonth
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerYear
@@ -433,9 +431,9 @@
                 Icon(
                     imageVector =
                         if (showConfirm) {
-                            Icons.Filled.Check
+                            Icons.Check
                         } else {
-                            Icons.AutoMirrored.Filled.KeyboardArrowRight
+                            Icons.AutoMirrored.KeyboardArrowRight
                         },
                     contentDescription =
                         if (showConfirm) {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
index 469df92..c25b822 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
@@ -107,7 +107,7 @@
  * @param modifier Modifier to be applied to the button. When animating the button to appear/
  *   disappear from the screen, a Modifier.height can be used to change the height of the component,
  *   but that won't change the space available for the content (though it may be scaled)
- * @param buttonHeight Defines the base size of the button, see the 4 standard sizes specified in
+ * @param preferredHeight Defines the base size of the button, see the 4 standard sizes specified in
  *   [ButtonDefaults]. This is used to determine the size constraints passed on to the content, and
  *   the size of the edge button if no height Modifier is specified. Note that if a height Modifier
  *   is specified for the EdgeButton, that will determine the size of the container, and the content
@@ -130,7 +130,7 @@
 fun EdgeButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
-    buttonHeight: Dp = ButtonDefaults.EdgeButtonHeightSmall,
+    preferredHeight: Dp = ButtonDefaults.EdgeButtonHeightSmall,
     enabled: Boolean = true,
     colors: ButtonColors = ButtonDefaults.buttonColors(),
     border: BorderStroke? = null,
@@ -143,10 +143,10 @@
     val screenWidthDp = screenWidthDp().dp
 
     val contentShapeHelper =
-        remember(buttonHeight) {
+        remember(preferredHeight) {
             ShapeHelper(density).apply {
                 // Compute the inner size using only the screen size and the buttonSize parameter
-                val size = with(density) { DpSize(screenWidthDp, buttonHeight).toSize() }
+                val size = with(density) { DpSize(screenWidthDp, preferredHeight).toSize() }
                 update(size)
             }
         }
@@ -178,7 +178,7 @@
                         } else {
                             screenWidthDp.roundToPx()
                         }
-                    val buttonHeightPx = with(density) { buttonHeight.roundToPx() }
+                    val buttonHeightPx = with(density) { preferredHeight.roundToPx() }
                     val size =
                         IntSize(
                             buttonWidthPx,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/HorizontalPageIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/HorizontalPageIndicator.kt
deleted file mode 100644
index ff584f5..0000000
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/HorizontalPageIndicator.kt
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * 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.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import androidx.wear.compose.foundation.CurvedAlignment
-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.angularSizeDp
-import androidx.wear.compose.foundation.background
-import androidx.wear.compose.foundation.curvedBox
-import androidx.wear.compose.foundation.curvedRow
-import androidx.wear.compose.foundation.radialSize
-import androidx.wear.compose.foundation.size
-import androidx.wear.compose.foundation.weight
-import androidx.wear.compose.material3.PageIndicatorDefaults.MaxNumberOfIndicators
-import androidx.wear.compose.materialcore.BoundsLimiter
-import androidx.wear.compose.materialcore.PagesState
-import androidx.wear.compose.materialcore.isLayoutDirectionRtl
-import androidx.wear.compose.materialcore.isRoundDevice
-import kotlin.math.roundToInt
-
-/**
- * Horizontal page indicator for use with [HorizontalPager], representing the currently active page
- * and the total number of pages. Pages are indicated as a Circle shape. The indicator shows up to
- * six pages individually - if there are more than six pages, [HorizontalPageIndicator] shows a
- * half-size indicator to the left or right to indicate that more are available.
- *
- * Here's how different positions 0..10 might be visually represented: "X" is selected item, "O" and
- * "o" full and half size items respectively.
- *
- * O X O O O o - 2nd position out of 10. There are no more items on the left but more on the right o
- * O O O X o - current page could be 6, 7 or 8 out of 10, as there are more possible items on the
- * left and on the right o O O O X O - current page is 9 out of 10, as there're no more items on the
- * right
- *
- * [HorizontalPageIndicator] can be linear or curved, depending on the screen shape of the device -
- * for circular screens it will be curved, whilst for square screens it will be linear.
- *
- * @sample androidx.wear.compose.material3.samples.HorizontalPageIndicatorSample
- *
- * Example usage with HorizontalPager:
- *
- * @sample androidx.wear.compose.material3.samples.HorizontalPageIndicatorWithPagerSample
- * @param pageCount Total number of pages
- * @param currentPage The currently selected page index
- * @param currentPageOffsetFraction The offset fraction of the currently selected page. Represents
- *   the offset as a fraction of the transition from the selected page to the next or previous page.
- *   Can be positive or negative.
- * @param modifier Modifier to be applied to the [HorizontalPageIndicator]
- * @param selectedColor The color of the selected [HorizontalPageIndicator] item
- * @param unselectedColor The color of unselected [HorizontalPageIndicator] items. Defaults to
- *   [selectedColor] with 30% alpha
- * @param indicatorSize The size of each [HorizontalPageIndicator] item in [Dp]
- * @param spacing The spacing between indicator items in [Dp]
- */
-@Composable
-public fun HorizontalPageIndicator(
-    pageCount: Int,
-    currentPage: Int,
-    currentPageOffsetFraction: () -> Float,
-    modifier: Modifier = Modifier,
-    selectedColor: Color = MaterialTheme.colorScheme.onBackground,
-    unselectedColor: Color = selectedColor.copy(alpha = 0.3f),
-    indicatorSize: Dp = 6.dp,
-    spacing: Dp = 4.dp
-) {
-    val isScreenRound = isRoundDevice()
-    val padding = PageIndicatorDefaults.edgePadding
-
-    // Converting offsetFraction into range 0..1f
-    val currentPageOffsetWithFraction = currentPage + currentPageOffsetFraction()
-    val selectedPage: Int = currentPageOffsetWithFraction.toInt()
-    val offset = currentPageOffsetWithFraction - selectedPage
-
-    val pagesOnScreen = Integer.min(MaxNumberOfIndicators, pageCount)
-    val pagesState =
-        remember(pageCount) { PagesState(totalPages = pageCount, pagesOnScreen = pagesOnScreen) }
-    pagesState.recalculateState(selectedPage, offset)
-
-    val leftSpacerSize = (indicatorSize + spacing) * pagesState.leftSpacerSizeRatio
-    val rightSpacerSize = (indicatorSize + spacing) * pagesState.rightSpacerSizeRatio
-
-    if (isScreenRound) {
-        var containerSize by remember { mutableStateOf(IntSize.Zero) }
-
-        val boundsSize: Density.() -> IntSize = {
-            val size =
-                IntSize(
-                    width = ((indicatorSize + spacing).toPx() * pagesOnScreen).roundToInt(),
-                    height = (indicatorSize * 2).toPx().roundToInt().coerceAtLeast(0)
-                )
-            size
-        }
-
-        val boundsOffset: Density.() -> IntOffset = {
-            val measuredSize = boundsSize()
-            // Offset here is the distance between top left corner of the outer container to
-            // the top left corner of the indicator. Its placement should look similar to
-            // Alignment.BottomCenter.
-            IntOffset(
-                x = (containerSize.width - measuredSize.width) / 2 - padding.toPx().toInt(),
-                y = containerSize.height - measuredSize.height - padding.toPx().toInt() * 2,
-            )
-        }
-
-        BoundsLimiter(
-            offset = boundsOffset,
-            size = boundsSize,
-            modifier = modifier.padding(padding),
-            onSizeChanged = { containerSize = it }
-        ) {
-            CurvedPageIndicator(
-                visibleDotIndex = pagesState.visibleDotIndex,
-                pagesOnScreen = pagesOnScreen,
-                indicator = { page ->
-                    curvedIndicator(
-                        page = page,
-                        size = indicatorSize,
-                        unselectedColor = unselectedColor,
-                        pagesState = pagesState
-                    )
-                },
-                itemsSpacer = { curvedSpacer(indicatorSize + spacing) },
-                selectedIndicator = {
-                    curvedSelectedIndicator(
-                        indicatorSize = indicatorSize,
-                        spacing = spacing,
-                        selectedColor = selectedColor,
-                        progress = offset
-                    )
-                },
-                spacerLeft = { curvedSpacer(leftSpacerSize) },
-                spacerRight = { curvedSpacer(rightSpacerSize) }
-            )
-        }
-    } else {
-        LinearPageIndicator(
-            modifier = modifier.padding(padding),
-            visibleDotIndex = pagesState.visibleDotIndex,
-            pagesOnScreen = pagesOnScreen,
-            indicator = { page ->
-                LinearIndicator(
-                    page = page,
-                    pagesState = pagesState,
-                    unselectedColor = unselectedColor,
-                    indicatorSize = indicatorSize,
-                    spacing = spacing,
-                )
-            },
-            selectedIndicator = {
-                LinearSelectedIndicator(
-                    indicatorSize = indicatorSize,
-                    spacing = spacing,
-                    selectedColor = selectedColor,
-                    progress = offset
-                )
-            },
-            spacerLeft = { LinearSpacer(leftSpacerSize) },
-            spacerRight = { LinearSpacer(rightSpacerSize) }
-        )
-    }
-}
-
-/** Contains the default values used by [HorizontalPageIndicator] */
-internal object PageIndicatorDefaults {
-
-    val MaxNumberOfIndicators = 6
-    internal val edgePadding = PaddingDefaults.edgePadding
-}
-
-@Composable
-private fun LinearPageIndicator(
-    modifier: Modifier,
-    visibleDotIndex: Int,
-    pagesOnScreen: Int,
-    indicator: @Composable (Int) -> Unit,
-    selectedIndicator: @Composable () -> Unit,
-    spacerLeft: @Composable () -> Unit,
-    spacerRight: @Composable () -> Unit
-) {
-    Box(
-        modifier = Modifier.fillMaxSize(),
-    ) {
-        Row(
-            modifier = modifier.align(Alignment.BottomCenter),
-            horizontalArrangement = Arrangement.Center,
-            verticalAlignment = Alignment.Bottom
-        ) {
-            // drawing 1 extra spacer for transition
-            spacerLeft()
-            for (page in 0 until visibleDotIndex) {
-                indicator(page)
-            }
-            Box(contentAlignment = Alignment.Center) {
-                Row(verticalAlignment = Alignment.Bottom) {
-                    indicator(visibleDotIndex)
-                    indicator(visibleDotIndex + 1)
-                }
-                Box { selectedIndicator() }
-            }
-            for (page in visibleDotIndex + 2..pagesOnScreen) {
-                indicator(page)
-            }
-            spacerRight()
-        }
-    }
-}
-
-@Composable
-private fun LinearSelectedIndicator(
-    indicatorSize: Dp,
-    spacing: Dp,
-    selectedColor: Color,
-    progress: Float
-) {
-    val horizontalPadding = spacing / 2
-    val isRtl = isLayoutDirectionRtl()
-    Spacer(
-        modifier =
-            Modifier.drawWithCache {
-                // Adding 2px to fully cover edges of non-selected indicators
-                val strokeWidth = indicatorSize.toPx() + 2
-                val startX = horizontalPadding.toPx() + strokeWidth / 2
-                val endX = this.size.width - horizontalPadding.toPx() - strokeWidth / 2
-                val drawWidth = endX - startX
-
-                val startSpacerWeight = (progress * 2 - 1).coerceAtLeast(0f)
-                val endSpacerWeight = (1 - progress * 2).coerceAtLeast(0f)
-
-                // Adding +1 or -1 for cases when start and end have the same coordinates -
-                // otherwise on APIs <= 26 line will not be drawn
-                val additionalPixel = if (isRtl) -1 else 1
-
-                val start =
-                    Offset(
-                        startX +
-                            drawWidth * (if (isRtl) startSpacerWeight else endSpacerWeight) +
-                            additionalPixel,
-                        this.size.height / 2
-                    )
-                val end =
-                    Offset(
-                        endX - drawWidth * (if (isRtl) endSpacerWeight else startSpacerWeight),
-                        this.size.height / 2
-                    )
-                onDrawBehind {
-                    drawLine(
-                        color = selectedColor,
-                        start = start,
-                        end = end,
-                        cap = StrokeCap.Round,
-                        strokeWidth = strokeWidth
-                    )
-                }
-            }
-    )
-}
-
-@Composable
-private fun LinearIndicator(
-    page: Int,
-    pagesState: PagesState,
-    unselectedColor: Color,
-    indicatorSize: Dp,
-    spacing: Dp,
-) {
-    Spacer(
-        modifier =
-            Modifier.padding(horizontal = spacing / 2).size(indicatorSize).drawWithCache {
-                val strokeWidth = indicatorSize.toPx() * pagesState.sizeRatio(page)
-                val start = Offset(strokeWidth / 2 + 1, this.size.height / 2)
-                val end = Offset(strokeWidth / 2, this.size.height / 2)
-                onDrawBehind {
-                    drawLine(
-                        color = unselectedColor,
-                        start = start,
-                        end = end,
-                        cap = StrokeCap.Round,
-                        alpha = pagesState.alpha(page),
-                        strokeWidth = strokeWidth
-                    )
-                }
-            }
-    )
-}
-
-@Composable
-private fun LinearSpacer(leftSpacerSize: Dp) {
-    Spacer(Modifier.size(leftSpacerSize, 0.dp))
-}
-
-@Composable
-private fun CurvedPageIndicator(
-    visibleDotIndex: Int,
-    pagesOnScreen: Int,
-    indicator: CurvedScope.(Int) -> Unit,
-    itemsSpacer: CurvedScope.() -> Unit,
-    selectedIndicator: CurvedScope.() -> Unit,
-    spacerLeft: CurvedScope.() -> Unit,
-    spacerRight: CurvedScope.() -> Unit
-) {
-    CurvedLayout(
-        modifier = Modifier,
-        // 90 degrees equals to 6 o'clock position, at the bottom of the screen
-        anchor = 90f,
-        angularDirection = CurvedDirection.Angular.Reversed
-    ) {
-        // drawing 1 extra spacer for transition
-        spacerLeft()
-
-        curvedRow(radialAlignment = CurvedAlignment.Radial.Center) {
-            for (page in 0 until visibleDotIndex) {
-                indicator(page)
-                itemsSpacer()
-            }
-            curvedBox(radialAlignment = CurvedAlignment.Radial.Center) {
-                curvedRow(radialAlignment = CurvedAlignment.Radial.Center) {
-                    indicator(visibleDotIndex)
-                    itemsSpacer()
-                    indicator(visibleDotIndex + 1)
-                }
-                selectedIndicator()
-            }
-            for (page in visibleDotIndex + 2..pagesOnScreen) {
-                itemsSpacer()
-                indicator(page)
-            }
-        }
-        spacerRight()
-    }
-}
-
-private fun CurvedScope.curvedSelectedIndicator(
-    indicatorSize: Dp,
-    spacing: Dp,
-    selectedColor: Color,
-    progress: Float
-) {
-
-    val startSpacerWeight = (1 - progress * 2).coerceAtLeast(0f)
-    val endSpacerWeight = (progress * 2 - 1).coerceAtLeast(0f)
-    val blurbWeight = (1 - startSpacerWeight - endSpacerWeight).coerceAtLeast(0.01f)
-
-    // Add 0.5dp to cover the sweepDegrees of unselected indicators
-    curvedRow(CurvedModifier.angularSizeDp(spacing + indicatorSize + 0.5.dp)) {
-        if (endSpacerWeight > 0f) {
-            curvedRow(CurvedModifier.weight(endSpacerWeight)) {}
-        }
-        curvedRow(
-            CurvedModifier.background(selectedColor, cap = StrokeCap.Round)
-                .weight(blurbWeight)
-                // Adding 0.3dp to fully cover edges of non-selected indicators
-                .radialSize(indicatorSize + 0.3.dp)
-        ) {}
-        if (startSpacerWeight > 0f) {
-            curvedRow(CurvedModifier.weight(startSpacerWeight)) {}
-        }
-    }
-}
-
-private fun CurvedScope.curvedIndicator(
-    page: Int,
-    unselectedColor: Color,
-    pagesState: PagesState,
-    size: Dp
-) {
-    curvedBox(
-        CurvedModifier
-            // Ideally we want sweepDegrees to be = 0f, because the circular shape is drawn
-            // by the Round StrokeCap.
-            // But it can't have 0f value due to limitations of underlying Canvas.
-            // Values below 0.2f also give some artifacts b/291753164
-            .size(0.2f, size * pagesState.sizeRatio(page))
-            .background(
-                color =
-                    unselectedColor.copy(alpha = unselectedColor.alpha * pagesState.alpha(page)),
-                cap = StrokeCap.Round
-            )
-    ) {}
-}
-
-private fun CurvedScope.curvedSpacer(size: Dp) {
-    curvedBox(CurvedModifier.angularSizeDp(size).radialSize(0.dp)) {}
-}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
index 63f2325..89839e3 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
@@ -16,6 +16,9 @@
 
 package androidx.wear.compose.material3
 
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
 internal object PaddingDefaults {
@@ -27,11 +30,31 @@
     val verticalContentPaddingPercentage = 10f
 
     /**
+     * Vertical padding between top and bottom edges of the screen and the content for full screen
+     * components, as a dp.
+     */
+    @Composable
+    fun verticalContentPadding(): Dp {
+        val screenHeight = LocalConfiguration.current.screenHeightDp
+        return screenHeight.dp * verticalContentPaddingPercentage / 100
+    }
+
+    /**
      * Horizontal padding between start and end edges of the screen and the content for full screen
      * components, as a percentage.
      */
     val horizontalContentPaddingPercentage = 5.2f
 
+    /**
+     * Horizontal padding between start and end edges of the screen and the content for full screen
+     * components, as a dp.
+     */
+    @Composable
+    fun horizontalContentPadding(): Dp {
+        val screenWidth = LocalConfiguration.current.screenWidthDp
+        return screenWidth.dp * horizontalContentPaddingPercentage / 100
+    }
+
     /** Default minimum padding between the edge of the screen and the content. */
     val edgePadding = 2.dp
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PageIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PageIndicator.kt
new file mode 100644
index 0000000..77f72e3
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PageIndicator.kt
@@ -0,0 +1,757 @@
+/*
+ * 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.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.CurvedAlignment
+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.angularSizeDp
+import androidx.wear.compose.foundation.background
+import androidx.wear.compose.foundation.curvedBox
+import androidx.wear.compose.foundation.curvedRow
+import androidx.wear.compose.foundation.padding
+import androidx.wear.compose.foundation.pager.PagerState
+import androidx.wear.compose.foundation.radialSize
+import androidx.wear.compose.foundation.size
+import androidx.wear.compose.foundation.weight
+import androidx.wear.compose.material3.pager.HorizontalPager
+import androidx.wear.compose.material3.pager.VerticalPager
+import androidx.wear.compose.material3.tokens.ColorSchemeKeyTokens
+import androidx.wear.compose.materialcore.BoundsLimiter
+import androidx.wear.compose.materialcore.isLayoutDirectionRtl
+import androidx.wear.compose.materialcore.isRoundDevice
+import kotlin.math.abs
+import kotlin.math.roundToInt
+
+/**
+ * Horizontal page indicator for use with [HorizontalPager], representing the currently active page
+ * and the approximate number of pages. Pages are indicated as a Circle shape. The indicator shows
+ * up to six pages individually - if there are more than six pages, [HorizontalPageIndicator] shows
+ * a smaller indicator to the left and/or right to indicate that more pages are available.
+ *
+ * This is a full screen component and will occupy the whole screen. However it's not actionable, so
+ * it's not expected to interfere with anything on the screen.
+ *
+ * Here's how different positions 0..10 might be visually represented: "X" is selected item, "O" and
+ * "o" full and half size items respectively.
+ *
+ * O X O O O o - 2nd position out of 10. There are no more items on the left but more on the right.
+ *
+ * o O O O X o - current page could be 6, 7 or 8 out of 10, as there are more potential pages on the
+ * left and on the right.
+ *
+ * o O O O X O - current page is 9 out of 10, as there no more items on the right
+ *
+ * [HorizontalPageIndicator] can be linear or curved, depending on the screen shape of the device -
+ * for circular screens it will be curved, whilst for square screens it will be linear.
+ *
+ * Example usage with [HorizontalPager]:
+ *
+ * @sample androidx.wear.compose.material3.samples.HorizontalPageIndicatorWithPagerSample
+ * @param pagerState State of the [HorizontalPager] used to control this indicator
+ * @param modifier Modifier to be applied to the [HorizontalPageIndicator]
+ * @param selectedColor The color which will be used for a selected indicator item.
+ * @param unselectedColor The color which will be used for an unselected indicator item.
+ * @param backgroundColor The color which will be used for an indicator background.
+ */
+@Composable
+fun HorizontalPageIndicator(
+    pagerState: PagerState,
+    modifier: Modifier = Modifier,
+    selectedColor: Color = PageIndicatorDefaults.selectedColor,
+    unselectedColor: Color = PageIndicatorDefaults.unselectedColor,
+    backgroundColor: Color = PageIndicatorDefaults.backgroundColor,
+) {
+    PageIndicatorImpl(
+        pagerState = pagerState,
+        selectedColor = selectedColor,
+        unselectedColor = unselectedColor,
+        backgroundColor = backgroundColor,
+        modifier = modifier,
+        indicatorSize = PageIndicatorItemSize,
+        spacing = PageIndicatorSpacing,
+        isHorizontal = true,
+    )
+}
+
+/**
+ * Vertical page indicator for use with [VerticalPager], representing the currently active page and
+ * the approximate number of pages. Pages are indicated as a Circle shape. The indicator shows up to
+ * six pages individually - if there are more than six pages, [VerticalPageIndicator] shows a
+ * smaller indicator to the top and/or bottom to indicate that more pages are available.
+ *
+ * This is a full screen component and will occupy the whole screen. However it's not actionable, so
+ * it's not expected to interfere with anything on the screen.
+ *
+ * [VerticalPageIndicator] can be linear or curved, depending on the screen shape of the device -
+ * for circular screens it will be curved, whilst for square screens it will be linear.
+ *
+ * Example usage with [VerticalPager]:
+ *
+ * @sample androidx.wear.compose.material3.samples.VerticalPageIndicatorWithPagerSample
+ * @param pagerState State of the [VerticalPager] used to control this indicator
+ * @param modifier Modifier to be applied to the [VerticalPageIndicator]
+ * @param selectedColor The color which will be used for a selected indicator item.
+ * @param unselectedColor The color which will be used for an unselected indicator item.
+ * @param backgroundColor The color which will be used for an indicator background.
+ */
+@Composable
+fun VerticalPageIndicator(
+    pagerState: PagerState,
+    modifier: Modifier = Modifier,
+    selectedColor: Color = PageIndicatorDefaults.selectedColor,
+    unselectedColor: Color = PageIndicatorDefaults.unselectedColor,
+    backgroundColor: Color = PageIndicatorDefaults.backgroundColor,
+) {
+    PageIndicatorImpl(
+        pagerState = pagerState,
+        selectedColor = selectedColor,
+        unselectedColor = unselectedColor,
+        backgroundColor = backgroundColor,
+        modifier = modifier,
+        indicatorSize = PageIndicatorItemSize,
+        spacing = PageIndicatorSpacing,
+        isHorizontal = false,
+    )
+}
+
+/** Contains the default values used by [HorizontalPageIndicator] and [VerticalPageIndicator] */
+object PageIndicatorDefaults {
+
+    /**
+     * The recommended color to use for the selected indicator item in [VerticalPageIndicator] and
+     * [HorizontalPageIndicator].
+     */
+    val selectedColor: Color
+        @ReadOnlyComposable @Composable get() = ColorSchemeKeyTokens.OnBackground.value
+
+    /**
+     * The recommended color to use for the unselected indicator item in [VerticalPageIndicator] and
+     * [HorizontalPageIndicator].
+     */
+    val unselectedColor: Color
+        @ReadOnlyComposable
+        @Composable
+        get() = ColorSchemeKeyTokens.OnBackground.value.copy(alpha = 0.3f)
+
+    /**
+     * The recommended color to use for the background in [VerticalPageIndicator] and
+     * [HorizontalPageIndicator].
+     */
+    val backgroundColor: Color
+        @ReadOnlyComposable
+        @Composable
+        get() = ColorSchemeKeyTokens.Background.value.copy(alpha = 0.85f)
+}
+
+@Composable
+internal fun PageIndicatorImpl(
+    pagerState: PagerState,
+    selectedColor: Color,
+    unselectedColor: Color,
+    backgroundColor: Color,
+    modifier: Modifier,
+    indicatorSize: Dp,
+    spacing: Dp,
+    isHorizontal: Boolean,
+) {
+    val isScreenRound = isRoundDevice()
+    val layoutDirection = LocalLayoutDirection.current
+    val edgePadding = PaddingDefaults.edgePadding
+
+    // Converting offsetFraction into range 0..1f
+    val currentPageOffsetWithFraction =
+        pagerState.currentPage + pagerState.currentPageOffsetFraction
+    val selectedPage: Int = currentPageOffsetWithFraction.toInt()
+    val offset = currentPageOffsetWithFraction - selectedPage
+
+    val pagesOnScreen = Integer.min(MaxNumberOfIndicators, pagerState.pageCount)
+    val pagesState =
+        remember(pagerState.pageCount) {
+            PagesState(
+                totalPages = pagerState.pageCount,
+                pagesOnScreen = pagesOnScreen,
+                smallIndicatorSize = smallIndicatorSize
+            )
+        }
+    pagesState.recalculateState(selectedPage, offset)
+
+    val leftSpacerSize = (indicatorSize + spacing) * pagesState.leftSpacerSizeRatio
+    val rightSpacerSize = (indicatorSize + spacing) * pagesState.rightSpacerSizeRatio
+
+    if (isScreenRound) {
+        var containerSize by remember { mutableStateOf(IntSize.Zero) }
+
+        val boundsSize: Density.() -> IntSize = {
+            val width = ((indicatorSize + spacing).toPx() * pagesOnScreen).roundToInt()
+            val height = (indicatorSize * 2).roundToPx().coerceAtLeast(0)
+            val size =
+                IntSize(
+                    width = if (isHorizontal) width else height,
+                    height = if (isHorizontal) height else width
+                )
+            size
+        }
+
+        val boundsOffset: Density.() -> IntOffset = {
+            val measuredSize = boundsSize()
+            if (isHorizontal) {
+                // Offset here is the distance between top left corner of the outer container to
+                // the top left corner of the indicator. Its placement should look similar to
+                // Alignment.BottomCenter.
+                IntOffset(
+                    x = (containerSize.width - measuredSize.width) / 2 - edgePadding.roundToPx(),
+                    y = containerSize.height - measuredSize.height - edgePadding.roundToPx() * 2,
+                )
+            } else {
+                // Offset here is the distance between top left corner of the outer container to
+                // the top left corner of the indicator. Its placement should look similar to
+                // Alignment.CenterEnd.
+                IntOffset(
+                    x =
+                        if (layoutDirection == LayoutDirection.Ltr) {
+                            containerSize.width - measuredSize.width - edgePadding.roundToPx() * 2
+                        } else edgePadding.roundToPx(),
+                    y = (containerSize.height - measuredSize.height) / 2 - edgePadding.roundToPx(),
+                )
+            }
+        }
+        // As we use an extra spacers to the start and end of horizontal indicator ( and higher and
+        // lower for vertical), we have to set their size in angular padding to compensate for that.
+        val angularPadding = -(spacing + indicatorSize)
+        BoundsLimiter(
+            offset = boundsOffset,
+            size = boundsSize,
+            modifier = modifier.padding(edgePadding),
+            onSizeChanged = { containerSize = it }
+        ) {
+            CurvedPageIndicator(
+                visibleDotIndex = pagesState.visibleDotIndex,
+                pagesOnScreen = pagesOnScreen,
+                indicator = { page ->
+                    curvedIndicator(
+                        page = page,
+                        size = indicatorSize,
+                        unselectedColor = unselectedColor,
+                        pagesState = pagesState
+                    )
+                },
+                itemsSpacer = { curvedSpacer(indicatorSize + spacing) },
+                selectedIndicator = {
+                    curvedSelectedIndicator(
+                        indicatorSize = indicatorSize,
+                        spacing = spacing,
+                        selectedColor = selectedColor,
+                        progress = offset
+                    )
+                },
+                spacerLeft = { curvedSpacer(leftSpacerSize) },
+                spacerRight = { curvedSpacer(rightSpacerSize) },
+                angularPadding = angularPadding,
+                isHorizontal = isHorizontal,
+                layoutDirection = layoutDirection,
+                backgroundColor = backgroundColor
+            )
+        }
+    } else {
+        LinearPageIndicator(
+            modifier = modifier.padding(vertical = edgePadding),
+            visibleDotIndex = pagesState.visibleDotIndex,
+            pagesOnScreen = pagesOnScreen,
+            indicator = { page ->
+                LinearIndicator(
+                    page = page,
+                    pagesState = pagesState,
+                    unselectedColor = unselectedColor,
+                    indicatorSize = indicatorSize,
+                    spacing = spacing,
+                )
+            },
+            selectedIndicator = {
+                LinearSelectedIndicator(
+                    indicatorSize = indicatorSize,
+                    spacing = spacing,
+                    selectedColor = selectedColor,
+                    progress = offset
+                )
+            },
+            spacerStart = { LinearSpacer(leftSpacerSize) },
+            spacerEnd = { LinearSpacer(rightSpacerSize) },
+            isHorizontal = isHorizontal,
+            layoutDirection = layoutDirection,
+            background = {
+                Box(
+                    modifier =
+                        Modifier.align(Alignment.BottomCenter)
+                            .size(
+                                width =
+                                    (pagesOnScreen * indicatorSize.value +
+                                            (pagesOnScreen - 1) * spacing.value)
+                                        .dp + BackgroundRadius * 2,
+                                height = indicatorSize + BackgroundRadius * 2
+                            )
+                            .background(color = backgroundColor, shape = RoundedCornerShape(50.dp))
+                )
+            },
+        )
+    }
+}
+
+// TODO(b/369535289) Fix a visual issue with linear indicator when there are more than 6 pages.
+@Composable
+private fun LinearPageIndicator(
+    modifier: Modifier,
+    visibleDotIndex: Int,
+    pagesOnScreen: Int,
+    indicator: @Composable (Int) -> Unit,
+    selectedIndicator: @Composable () -> Unit,
+    spacerStart: @Composable () -> Unit,
+    spacerEnd: @Composable () -> Unit,
+    isHorizontal: Boolean,
+    layoutDirection: LayoutDirection,
+    background: @Composable BoxScope.() -> Unit
+) {
+    val width = LocalConfiguration.current.screenWidthDp
+    val height = LocalConfiguration.current.screenHeightDp
+    Box(Modifier.fillMaxSize()) {
+        Box(
+            modifier =
+                modifier.let {
+                    if (isHorizontal) it.size(width = width.dp, height = height.dp)
+                    // Flip width and height so that the indicator will fit into rectangular screen,
+                    // rotate it -90 degrees, and flip it vertically
+                    else
+                        it.size(width = height.dp, height = width.dp)
+                            .align(Alignment.Center)
+                            .graphicsLayer {
+                                rotationZ =
+                                    if (layoutDirection == LayoutDirection.Ltr) -90f else 90f
+                                scaleX = -1f
+                                scaleY = 1f
+                            }
+                }
+        ) {
+            background()
+            Row(
+                modifier =
+                    Modifier.padding(bottom = BackgroundRadius).align(Alignment.BottomCenter),
+                horizontalArrangement = Arrangement.Center,
+                verticalAlignment = Alignment.Bottom,
+            ) {
+                // drawing 1 extra spacer for transition
+                spacerStart()
+                for (page in 0 until visibleDotIndex) {
+                    indicator(page)
+                }
+                Box(contentAlignment = Alignment.Center) {
+                    Row(verticalAlignment = Alignment.Bottom) {
+                        indicator(visibleDotIndex)
+                        indicator(visibleDotIndex + 1)
+                    }
+                    Box { selectedIndicator() }
+                }
+                for (page in visibleDotIndex + 2..pagesOnScreen) {
+                    indicator(page)
+                }
+                spacerEnd()
+            }
+        }
+    }
+}
+
+@Composable
+private fun LinearSelectedIndicator(
+    indicatorSize: Dp,
+    spacing: Dp,
+    selectedColor: Color,
+    progress: Float
+) {
+    val horizontalPadding = spacing / 2
+    val isRtl = isLayoutDirectionRtl()
+    Spacer(
+        modifier =
+            Modifier.drawWithCache {
+                // Adding 2px to fully cover edges of non-selected indicators
+                val strokeWidth = indicatorSize.toPx() + 2
+                val startX = horizontalPadding.toPx() + strokeWidth / 2
+                val endX = this.size.width - horizontalPadding.toPx() - strokeWidth / 2
+                val drawWidth = endX - startX
+
+                val startSpacerWeight = (progress * 2 - 1).coerceAtLeast(0f)
+                val endSpacerWeight = (1 - progress * 2).coerceAtLeast(0f)
+
+                // Adding +1 or -1 for cases when start and end have the same coordinates -
+                // otherwise on APIs <= 26 line will not be drawn
+                val additionalPixel = if (isRtl) -1 else 1
+
+                val start =
+                    Offset(
+                        startX +
+                            drawWidth * (if (isRtl) startSpacerWeight else endSpacerWeight) +
+                            additionalPixel,
+                        this.size.height / 2
+                    )
+                val end =
+                    Offset(
+                        endX - drawWidth * (if (isRtl) endSpacerWeight else startSpacerWeight),
+                        this.size.height / 2
+                    )
+                onDrawBehind {
+                    drawLine(
+                        color = selectedColor,
+                        start = start,
+                        end = end,
+                        cap = StrokeCap.Round,
+                        strokeWidth = strokeWidth
+                    )
+                }
+            }
+    )
+}
+
+@Composable
+private fun LinearIndicator(
+    page: Int,
+    pagesState: PagesState,
+    unselectedColor: Color,
+    indicatorSize: Dp,
+    spacing: Dp,
+) {
+    Spacer(
+        modifier =
+            Modifier.padding(horizontal = spacing / 2).size(indicatorSize).drawWithCache {
+                val strokeWidth = indicatorSize.toPx() * pagesState.sizeRatio(page)
+                val start = Offset(strokeWidth / 2 + 1, this.size.height / 2)
+                val end = Offset(strokeWidth / 2, this.size.height / 2)
+                onDrawBehind {
+                    drawLine(
+                        color = unselectedColor,
+                        start = start,
+                        end = end,
+                        cap = StrokeCap.Round,
+                        alpha = pagesState.alpha(page),
+                        strokeWidth = strokeWidth
+                    )
+                }
+            }
+    )
+}
+
+@Composable
+private fun LinearSpacer(leftSpacerSize: Dp) {
+    Spacer(Modifier.size(leftSpacerSize, 0.dp))
+}
+
+@Composable
+private fun CurvedPageIndicator(
+    visibleDotIndex: Int,
+    pagesOnScreen: Int,
+    indicator: CurvedScope.(Int) -> Unit,
+    itemsSpacer: CurvedScope.() -> Unit,
+    selectedIndicator: CurvedScope.() -> Unit,
+    spacerLeft: CurvedScope.() -> Unit,
+    spacerRight: CurvedScope.() -> Unit,
+    isHorizontal: Boolean,
+    layoutDirection: LayoutDirection,
+    angularPadding: Dp,
+    backgroundColor: Color,
+) {
+    val anchor =
+        if (isHorizontal) HorizontalPagerAnchor
+        else {
+            if (layoutDirection == LayoutDirection.Ltr) VerticalPagerAnchor
+            else VerticalPagerRtlAnchor
+        }
+    val angularDirection =
+        if (isHorizontal) CurvedDirection.Angular.Reversed else CurvedDirection.Angular.Normal
+
+    CurvedLayout(modifier = Modifier, anchor = anchor, angularDirection = angularDirection) {
+        // drawing 1 extra spacer for transition
+        curvedRow(
+            modifier =
+                CurvedModifier.background(backgroundColor, cap = StrokeCap.Round)
+                    .padding(radial = BackgroundRadius, angular = angularPadding)
+        ) {
+            spacerLeft()
+            curvedRow(radialAlignment = CurvedAlignment.Radial.Center) {
+                for (page in 0 until visibleDotIndex) {
+                    indicator(page)
+                    itemsSpacer()
+                }
+                curvedBox(radialAlignment = CurvedAlignment.Radial.Center) {
+                    curvedRow(radialAlignment = CurvedAlignment.Radial.Center) {
+                        indicator(visibleDotIndex)
+                        itemsSpacer()
+                        indicator(visibleDotIndex + 1)
+                    }
+                    selectedIndicator()
+                }
+                for (page in visibleDotIndex + 2..pagesOnScreen) {
+                    itemsSpacer()
+                    indicator(page)
+                }
+            }
+            spacerRight()
+        }
+    }
+}
+
+private fun CurvedScope.curvedSelectedIndicator(
+    indicatorSize: Dp,
+    spacing: Dp,
+    selectedColor: Color,
+    progress: Float
+) {
+
+    val startSpacerWeight = (1 - progress * 2).coerceAtLeast(0f)
+    val endSpacerWeight = (progress * 2 - 1).coerceAtLeast(0f)
+    val blurbWeight = (1 - startSpacerWeight - endSpacerWeight).coerceAtLeast(0.01f)
+
+    // Add 0.5dp to cover the sweepDegrees of unselected indicators
+    curvedRow(CurvedModifier.angularSizeDp(spacing + indicatorSize + 0.5.dp)) {
+        if (endSpacerWeight > 0f) {
+            curvedRow(CurvedModifier.weight(endSpacerWeight)) {}
+        }
+        curvedRow(
+            CurvedModifier.background(selectedColor, cap = StrokeCap.Round)
+                .weight(blurbWeight)
+                // Adding 0.3dp to fully cover edges of non-selected indicators
+                .radialSize(indicatorSize + 0.3.dp)
+        ) {}
+        if (startSpacerWeight > 0f) {
+            curvedRow(CurvedModifier.weight(startSpacerWeight)) {}
+        }
+    }
+}
+
+private fun CurvedScope.curvedIndicator(
+    page: Int,
+    unselectedColor: Color,
+    pagesState: PagesState,
+    size: Dp
+) {
+    curvedBox(
+        CurvedModifier
+            // Ideally we want sweepDegrees to be = 0f, because the circular shape is drawn
+            // by the Round StrokeCap.
+            // But it can't have 0f value due to limitations of underlying Canvas.
+            // Values below 0.2f also give some artifacts b/291753164
+            .size(0.2f, size * pagesState.sizeRatio(page))
+            .background(
+                color =
+                    unselectedColor.copy(alpha = unselectedColor.alpha * pagesState.alpha(page)),
+                cap = StrokeCap.Round
+            )
+    ) {}
+}
+
+private fun CurvedScope.curvedSpacer(size: Dp) {
+    curvedBox(CurvedModifier.angularSizeDp(size).radialSize(0.dp)) {}
+}
+
+/**
+ * Represents an internal state of pageIndicator. This state is responsible for keeping and
+ * recalculating alpha and size parameters of each indicator, and selected indicators as well.
+ */
+private class PagesState(
+    val totalPages: Int,
+    val pagesOnScreen: Int,
+    val smallIndicatorSize: Float
+) {
+    // Sizes and alphas of first and last indicators on the screen. Used to show that there're more
+    // pages on the left or on the right, and also for smooth transitions
+    private var firstAlpha = 1f
+    private var lastAlpha = 0f
+    private var firstSize = 1f
+    private var secondSize = 1f
+    private var lastSize = 1f
+    private var lastButOneSize = 1f
+
+    private var smoothProgress = 0f
+
+    // An offset in pages, basically meaning how many pages are hidden to the left.
+    private var hiddenPagesToTheLeft = 0
+
+    // A default size of spacers - invisible items to the left and to the right of
+    // visible indicators, used for smooth transitions
+
+    // Current visible position on the screen.
+    var visibleDotIndex = 0
+        private set
+
+    // A size of a left spacer used for smooth transitions
+    val leftSpacerSizeRatio
+        get() = 1 - smoothProgress
+
+    // A size of a right spacer used for smooth transitions
+    val rightSpacerSizeRatio
+        get() = smoothProgress
+
+    /**
+     * Depending on the page index, return an alpha for this indicator
+     *
+     * @param page Page index
+     * @return An alpha of page index- in range 0..1
+     */
+    fun alpha(page: Int): Float =
+        when (page) {
+            0 -> firstAlpha
+            pagesOnScreen -> lastAlpha
+            else -> 1f
+        }
+
+    /**
+     * Depending on the page index, return a size ratio for this indicator
+     *
+     * @param page Page index
+     * @return An size ratio for page index - in range 0..1
+     */
+    fun sizeRatio(page: Int): Float =
+        when (page) {
+            0 -> firstSize
+            1 -> secondSize
+            pagesOnScreen - 1 -> lastButOneSize
+            pagesOnScreen -> lastSize
+            else -> 1f
+        }
+
+    /**
+     * Returns a value in the range 0..1 where 0 is unselected state, and 1 is selected. Used to
+     * show a smooth transition between page indicator items.
+     */
+    fun calculateSelectedRatio(targetPage: Int, offset: Float): Float =
+        (1 - abs(visibleDotIndex + offset - targetPage)).coerceAtLeast(0f)
+
+    // Main function responsible for recalculation of all parameters regarding
+    // to the [selectedPage] and [offset]
+    fun recalculateState(selectedPage: Int, offset: Float) {
+        val pageWithOffset = selectedPage + offset
+        // Calculating offsetInPages relating to the [selectedPage].
+
+        // For example, for [selectedPage] = 4 we will see this picture :
+        // O O O O X o. [offsetInPages] will be 0.
+        // But when [selectedPage] will be incremented to 5, it will be seen as
+        // o O O O X o, with [offsetInPages] = 1
+        if (selectedPage > hiddenPagesToTheLeft + pagesOnScreen - 2) {
+            // Set an offset as a difference between current page and pages on the screen,
+            // except if this is not the last page - then offsetInPages is not changed
+            hiddenPagesToTheLeft =
+                (selectedPage - (pagesOnScreen - 2)).coerceAtMost(totalPages - pagesOnScreen)
+        } else if (pageWithOffset <= hiddenPagesToTheLeft) {
+            hiddenPagesToTheLeft = (selectedPage - 1).coerceAtLeast(0)
+        }
+
+        // Condition for scrolling to the right. A smooth scroll to the right is only triggered
+        // when we have more than 2 pages to the right, and currently we're on the right edge.
+        // For example -> o O O O X o -> a small "o" shows that there're more pages to the right
+        val scrolledToTheRight =
+            pageWithOffset > hiddenPagesToTheLeft + pagesOnScreen - 2 &&
+                pageWithOffset < totalPages - 2
+
+        // Condition for scrolling to the left. A smooth scroll to the left is only triggered
+        // when we have more than 2 pages to the left, and currently we're on the left edge.
+        // For example -> o X O O O o -> a small "o" shows that there're more pages to the left
+        val scrolledToTheLeft = pageWithOffset > 1 && pageWithOffset < hiddenPagesToTheLeft + 1
+
+        smoothProgress = if (scrolledToTheLeft || scrolledToTheRight) offset else 0f
+
+        // Calculating exact parameters for border indicators like [firstAlpha], [lastSize], etc.
+        firstAlpha = 1 - smoothProgress
+        lastAlpha = smoothProgress
+        secondSize = 1 - (1 - smallIndicatorSize) * smoothProgress
+
+        // Depending on offsetInPages we'll either show a shrinked first indicator, or full-size
+        firstSize =
+            if (hiddenPagesToTheLeft == 0 || hiddenPagesToTheLeft == 1 && scrolledToTheLeft) {
+                1 - smoothProgress
+            } else {
+                smallIndicatorSize * (1 - smoothProgress)
+            }
+
+        // Depending on offsetInPages and other parameters, we'll either show a shrinked
+        // last indicator, or full-size
+        lastSize =
+            if (
+                hiddenPagesToTheLeft == totalPages - pagesOnScreen - 1 && scrolledToTheRight ||
+                    hiddenPagesToTheLeft == totalPages - pagesOnScreen && scrolledToTheLeft
+            ) {
+                smoothProgress
+            } else {
+                smallIndicatorSize * smoothProgress
+            }
+
+        lastButOneSize =
+            if (scrolledToTheRight || scrolledToTheLeft) {
+                lerp(smallIndicatorSize, 1f, smoothProgress)
+            } else if (hiddenPagesToTheLeft < totalPages - pagesOnScreen) smallIndicatorSize else 1f
+
+        // A visibleDot represents a currently selected page on the screen
+        // As we scroll to the left, we add an invisible indicator to the left, shifting all other
+        // indicators to the right. The shift is only possible when a visibleDot = 1,
+        // thus we have to leave it at 1 as we always add a positive offset
+        visibleDotIndex = if (scrolledToTheLeft) 1 else selectedPage - hiddenPagesToTheLeft
+    }
+}
+
+private const val smallIndicatorSize = 0.66f
+private const val MaxNumberOfIndicators = 6
+
+// 0 degrees equals to 3 o'clock position, at the right of the screen
+private val VerticalPagerAnchor = 0f
+// 180 degrees equals to 9 o'clock position, at the left of the screen
+private val VerticalPagerRtlAnchor = 180f
+// 90 degrees equals to 6 o'clock position, at the bottom of the screen
+private val HorizontalPagerAnchor = 90f
+/** The default size of the indicator */
+internal val PageIndicatorItemSize = 6.dp
+/** The default spacing between the indicators */
+internal val PageIndicatorSpacing = 4.dp
+internal val BackgroundRadius = 3.dp
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index 6326651..dd4ff33 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -28,8 +28,6 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -52,6 +50,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.SliderDefaults.MaxSegmentSteps
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Strings.Companion.SliderDecreaseButtonContentDescription
 import androidx.wear.compose.material3.internal.Strings.Companion.SliderIncreaseButtonContentDescription
 import androidx.wear.compose.material3.internal.getString
@@ -60,7 +59,6 @@
 import androidx.wear.compose.materialcore.InlineSliderButton
 import androidx.wear.compose.materialcore.RangeDefaults.calculateCurrentStepValue
 import androidx.wear.compose.materialcore.RangeDefaults.snapValueToStep
-import androidx.wear.compose.materialcore.RangeIcons
 import androidx.wear.compose.materialcore.directedValue
 import kotlin.math.roundToInt
 
@@ -228,8 +226,9 @@
  *
  * The bar in the middle of control can have separators if [segmented] flag is set to true. A number
  * of steps is calculated as the difference between max and min values of [valueProgression] divided
- * by [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of
- * steps will be (120-100)/ 5 - 1 = 3. Steps are 100(first), 105, 110, 115, 120(last)
+ * by [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of by
+ * [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of steps
+ * will be (120-100)/ 5 - 1 = 3. Steps are 100(first), 105, 110, 115, 120(last)
  *
  * If [valueProgression] range is not equally divisible by [valueProgression].step, then
  * [valueProgression].last will be adjusted to the closest divisible value in the range. For
@@ -306,7 +305,7 @@
     @Composable
     fun DecreaseIcon(modifier: Modifier = Modifier) =
         Icon(
-            RangeIcons.Minus,
+            Icons.Remove,
             getString(SliderDecreaseButtonContentDescription),
             modifier.size(IconSize)
         )
@@ -314,11 +313,7 @@
     /** Increase Icon. */
     @Composable
     fun IncreaseIcon(modifier: Modifier = Modifier) =
-        Icon(
-            Icons.Filled.Add,
-            getString(SliderIncreaseButtonContentDescription),
-            modifier.size(IconSize)
-        )
+        Icon(Icons.Add, getString(SliderIncreaseButtonContentDescription), modifier.size(IconSize))
 
     /**
      * Creates a [SliderColors] that represents the default background and content colors used in an
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwipeToReveal.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwipeToReveal.kt
index 5027491..1c27ebf 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwipeToReveal.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwipeToReveal.kt
@@ -57,6 +57,7 @@
 import androidx.wear.compose.foundation.RevealActionType
 import androidx.wear.compose.foundation.RevealState
 import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.SwipeDirection
 import androidx.wear.compose.foundation.SwipeToReveal
 import androidx.wear.compose.foundation.createAnchors
 import androidx.wear.compose.material3.ButtonDefaults.buttonColors
@@ -311,6 +312,7 @@
  * @param useAnchoredActions Whether the actions should stay revealed, or bounce back to hidden when
  *   the user stops swiping. This is relevant for SwipeToReveal components with a single action. If
  *   the developer wants a swipe to clear behaviour, this should be set to false.
+ * @param swipeDirection Direction of the swipe to reveal the actions.
  */
 @OptIn(ExperimentalWearFoundationApi::class)
 @Composable
@@ -318,6 +320,7 @@
     initialValue: RevealValue = RevealValue.Covered,
     anchorWidth: Dp = SwipeToRevealDefaults.SingleActionAnchorWidth,
     useAnchoredActions: Boolean = true,
+    swipeDirection: SwipeDirection = SwipeDirection.RightToLeft,
 ): RevealState {
     val anchorFraction = anchorWidth.value / screenWidthDp()
     return androidx.wear.compose.foundation.rememberRevealState(
@@ -326,7 +329,8 @@
         anchors =
             createAnchors(
                 revealingAnchor = if (useAnchoredActions) anchorFraction else 0f,
-            )
+                swipeDirection = swipeDirection,
+            ),
     )
 }
 
@@ -423,7 +427,13 @@
                     } else {
                         if (hasUndo || revealActionType == RevealActionType.PrimaryAction) {
                             revealState.lastActionType = revealActionType
-                            revealState.animateTo(RevealValue.Revealed)
+                            revealState.animateTo(
+                                if (revealState.offset > 0) {
+                                    RevealValue.LeftRevealed
+                                } else {
+                                    RevealValue.RightRevealed
+                                }
+                            )
                         }
                     }
                 } finally {
@@ -463,7 +473,8 @@
                         }
                     primaryActionTextRevealed.value =
                         abs(revealState.offset) > minimumOffsetToRevealPx &&
-                            revealState.targetValue == RevealValue.Revealed
+                            (revealState.targetValue == RevealValue.RightRevealed ||
+                                revealState.targetValue == RevealValue.LeftRevealed)
                 }
             }
         }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
index cffdb96..cb1daac 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -33,8 +33,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
@@ -63,6 +61,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.max
 import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Plurals
 import androidx.wear.compose.material3.internal.Strings
 import androidx.wear.compose.material3.internal.getPlurals
@@ -333,7 +332,7 @@
                         }
                         .focusRequester(focusRequesterConfirmButton)
                         .focusable(),
-                buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+                preferredHeight = ButtonDefaults.EdgeButtonHeightSmall,
                 colors =
                     buttonColors(
                         contentColor = colors.confirmButtonContentColor,
@@ -341,7 +340,7 @@
                     ),
             ) {
                 Icon(
-                    imageVector = Icons.Filled.Check,
+                    imageVector = Icons.Check,
                     contentDescription = getString(Strings.PickerConfirmButtonContentDescription),
                     modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
                 )
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
new file mode 100644
index 0000000..8825d23
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.internal
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.PathBuilder
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+
+internal object Icons {
+    internal val Add: ImageVector
+        get() {
+            if (_add != null) {
+                return _add!!
+            }
+            _add =
+                materialIcon(name = "Add") {
+                    materialPath {
+                        moveTo(440f, 520f)
+                        lineTo(240f, 520f)
+                        quadTo(223f, 520f, 211.5f, 508.5f)
+                        quadTo(200f, 497f, 200f, 480f)
+                        quadTo(200f, 463f, 211.5f, 451.5f)
+                        quadTo(223f, 440f, 240f, 440f)
+                        lineTo(440f, 440f)
+                        lineTo(440f, 240f)
+                        quadTo(440f, 223f, 451.5f, 211.5f)
+                        quadTo(463f, 200f, 480f, 200f)
+                        quadTo(497f, 200f, 508.5f, 211.5f)
+                        quadTo(520f, 223f, 520f, 240f)
+                        lineTo(520f, 440f)
+                        lineTo(720f, 440f)
+                        quadTo(737f, 440f, 748.5f, 451.5f)
+                        quadTo(760f, 463f, 760f, 480f)
+                        quadTo(760f, 497f, 748.5f, 508.5f)
+                        quadTo(737f, 520f, 720f, 520f)
+                        lineTo(520f, 520f)
+                        lineTo(520f, 720f)
+                        quadTo(520f, 737f, 508.5f, 748.5f)
+                        quadTo(497f, 760f, 480f, 760f)
+                        quadTo(463f, 760f, 451.5f, 748.5f)
+                        quadTo(440f, 737f, 440f, 720f)
+                        lineTo(440f, 520f)
+                        close()
+                    }
+                }
+            return _add!!
+        }
+
+    private var _add: ImageVector? = null
+
+    internal val Remove: ImageVector
+        get() {
+            if (_remove != null) {
+                return _remove!!
+            }
+            _remove =
+                materialIcon(name = "Remove") {
+                    materialPath {
+                        moveTo(240f, 520f)
+                        quadTo(223f, 520f, 211.5f, 508.5f)
+                        quadTo(200f, 497f, 200f, 480f)
+                        quadTo(200f, 463f, 211.5f, 451.5f)
+                        quadTo(223f, 440f, 240f, 440f)
+                        lineTo(720f, 440f)
+                        quadTo(737f, 440f, 748.5f, 451.5f)
+                        quadTo(760f, 463f, 760f, 480f)
+                        quadTo(760f, 497f, 748.5f, 508.5f)
+                        quadTo(737f, 520f, 720f, 520f)
+                        lineTo(240f, 520f)
+                        close()
+                    }
+                }
+            return _remove!!
+        }
+
+    private var _remove: ImageVector? = null
+
+    internal val Check: ImageVector
+        get() {
+            if (_check != null) {
+                return _check!!
+            }
+            _check =
+                materialIcon(name = "Check") {
+                    materialPath {
+                        moveTo(382f, 597.87f)
+                        lineTo(716.7f, 263.17f)
+                        quadTo(730.37f, 249.5f, 748.76f, 249.5f)
+                        quadTo(767.15f, 249.5f, 780.83f, 263.17f)
+                        quadTo(794.5f, 276.85f, 794.5f, 295.62f)
+                        quadTo(794.5f, 314.39f, 780.83f, 328.07f)
+                        lineTo(414.07f, 695.59f)
+                        quadTo(400.39f, 709.26f, 382f, 709.26f)
+                        quadTo(363.61f, 709.26f, 349.93f, 695.59f)
+                        lineTo(178.41f, 524.07f)
+                        quadTo(164.74f, 510.39f, 165.12f, 491.62f)
+                        quadTo(165.5f, 472.85f, 179.17f, 459.17f)
+                        quadTo(192.85f, 445.5f, 211.62f, 445.5f)
+                        quadTo(230.39f, 445.5f, 244.07f, 459.17f)
+                        lineTo(382f, 597.87f)
+                        close()
+                    }
+                }
+            return _check!!
+        }
+
+    private var _check: ImageVector? = null
+
+    internal object AutoMirrored {
+        internal val KeyboardArrowRight: ImageVector
+            get() {
+                if (_keyboardArrowRight != null) {
+                    return _keyboardArrowRight!!
+                }
+                _keyboardArrowRight =
+                    materialIcon(
+                        name = "AutoMirrored.KeyboardArrowRight",
+                        autoMirror = true,
+                    ) {
+                        materialPath {
+                            moveTo(496.35f, 480f)
+                            lineTo(344.17f, 327.83f)
+                            quadTo(331.5f, 315.15f, 331.5f, 296f)
+                            quadTo(331.5f, 276.85f, 344.17f, 264.17f)
+                            quadTo(356.85f, 251.5f, 376f, 251.5f)
+                            quadTo(395.15f, 251.5f, 407.83f, 264.17f)
+                            lineTo(591.59f, 447.93f)
+                            quadTo(598.3f, 454.65f, 601.4f, 462.85f)
+                            quadTo(604.5f, 471.04f, 604.5f, 480f)
+                            quadTo(604.5f, 488.96f, 601.4f, 497.15f)
+                            quadTo(598.3f, 505.35f, 591.59f, 512.07f)
+                            lineTo(407.83f, 695.83f)
+                            quadTo(395.15f, 708.5f, 376f, 708.5f)
+                            quadTo(356.85f, 708.5f, 344.17f, 695.83f)
+                            quadTo(331.5f, 683.15f, 331.5f, 664f)
+                            quadTo(331.5f, 644.85f, 344.17f, 632.17f)
+                            lineTo(496.35f, 480f)
+                            close()
+                        }
+                    }
+                return _keyboardArrowRight!!
+            }
+
+        private var _keyboardArrowRight: ImageVector? = null
+    }
+}
+
+private inline fun materialIcon(
+    name: String,
+    autoMirror: Boolean = false,
+    block: ImageVector.Builder.() -> ImageVector.Builder
+): ImageVector =
+    ImageVector.Builder(
+            name = name,
+            defaultWidth = MaterialIconDimension,
+            defaultHeight = MaterialIconDimension,
+            viewportWidth = MaterialIconViewPointDimension,
+            viewportHeight = MaterialIconViewPointDimension,
+            autoMirror = autoMirror
+        )
+        .block()
+        .build()
+
+private inline fun ImageVector.Builder.materialPath(pathBuilder: PathBuilder.() -> Unit) =
+    path(fill = SolidColor(Color.White), pathBuilder = pathBuilder)
+
+private val MaterialIconDimension = 24.dp
+private const val MaterialIconViewPointDimension = 960f
diff --git a/wear/compose/compose-material3/src/main/res/drawable/wear_m3c_open_on_phone_animation.xml b/wear/compose/compose-material3/src/main/res/drawable/wear_m3c_open_on_phone_animation.xml
index 26c7f42..d9f95e8 100644
--- a/wear/compose/compose-material3/src/main/res/drawable/wear_m3c_open_on_phone_animation.xml
+++ b/wear/compose/compose-material3/src/main/res/drawable/wear_m3c_open_on_phone_animation.xml
@@ -14,4 +14,4 @@
   limitations under the License.
   -->
 
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="800dp" android:width="800dp" android:viewportHeight="800" android:viewportWidth="800"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="372" android:translateY="396" android:scaleX="9.86" android:scaleY="9.86"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-7.44 23.29 C-8.45,23.29 -9.32,22.93 -10.04,22.21 C-10.76,21.49 -11.13,20.62 -11.13,19.61 C-11.13,19.61 -11.13,8.18 -11.13,8.18 C-11.13,7.71 -10.96,7.31 -10.64,6.99 C-10.31,6.66 -9.91,6.5 -9.45,6.5 C-8.98,6.5 -8.6,6.66 -8.31,6.99 C-7.98,7.31 -7.82,7.71 -7.82,8.18 C-7.82,8.18 -7.82,14.46 -7.82,14.46 C-7.82,14.46 14.82,14.46 14.82,14.46 C14.82,14.46 14.82,-14.46 14.82,-14.46 C14.82,-14.46 -7.82,-14.46 -7.82,-14.46 C-7.82,-14.46 -7.82,-8.18 -7.82,-8.18 C-7.82,-7.71 -7.98,-7.31 -8.31,-6.99 C-8.6,-6.66 -8.98,-6.5 -9.45,-6.5 C-9.91,-6.5 -10.31,-6.66 -10.64,-6.99 C-10.96,-7.31 -11.13,-7.71 -11.13,-8.18 C-11.13,-8.18 -11.13,-19.61 -11.13,-19.61 C-11.13,-20.62 -10.76,-21.49 -10.04,-22.21 C-9.32,-22.93 -8.45,-23.29 -7.44,-23.29 C-7.44,-23.29 14.44,-23.29 14.44,-23.29 C15.45,-23.29 16.32,-22.93 17.04,-22.21 C17.76,-21.49 18.12,-20.62 18.12,-19.61 C18.12,-19.61 18.12,19.61 18.12,19.61 C18.12,20.62 17.76,21.49 17.04,22.21 C16.32,22.93 15.45,23.29 14.44,23.29 C14.44,23.29 -7.44,23.29 -7.44,23.29c  M-7.82 17.77 C-7.82,17.77 -7.82,19.61 -7.82,19.61 C-7.82,19.72 -7.78,19.81 -7.71,19.88 C-7.64,19.95 -7.55,19.99 -7.44,19.99 C-7.44,19.99 14.44,19.99 14.44,19.99 C14.55,19.99 14.64,19.95 14.71,19.88 C14.78,19.81 14.82,19.72 14.82,19.61 C14.82,19.61 14.82,17.77 14.82,17.77 C14.82,17.77 -7.82,17.77 -7.82,17.77c  M-7.82 -17.77 C-7.82,-17.77 14.82,-17.77 14.82,-17.77 C14.82,-17.77 14.82,-19.61 14.82,-19.61 C14.82,-19.72 14.78,-19.81 14.71,-19.88 C14.64,-19.95 14.55,-19.99 14.44,-19.99 C14.44,-19.99 -7.44,-19.99 -7.44,-19.99 C-7.55,-19.99 -7.64,-19.95 -7.71,-19.88 C-7.78,-19.81 -7.82,-19.72 -7.82,-19.61 C-7.82,-19.61 -7.82,-17.77 -7.82,-17.77c "/></group><group android:name="_R_G_L_1_G" android:translateX="370" android:translateY="428" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#003352" android:fillAlpha="1" android:fillType="nonZero" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M-189 -33 C-189,-33 20,-33 20,-33 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="30" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M-189 -33 C-189,-33 20,-33 20,-33 "/></group><group android:name="_R_G_L_0_G_T_1" android:translateX="180" android:translateY="395" android:scaleY="0"><group android:name="_R_G_L_0_G" android:translateX="-20" android:translateY="33"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="32" android:strokeAlpha="1" android:trimPathStart="0.49" android:trimPathEnd="0.51" android:trimPathOffset="0" android:pathData=" M-36 -90 C-36,-90 21,-33 21,-33 C21,-33 -36,24 -36,24 "/></group></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="217" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="217" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateXY" android:duration="67" android:startOffset="0" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 370,428C 370,428 370,428 370,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="217" android:startOffset="67" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 370,428C 370,428 412,428 412,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="200" android:startOffset="283" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 412,428C 412,428 400,428 400,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.44,0 0.55,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="133" android:startOffset="0" android:valueFrom="0.49" android:valueTo="0.49" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathStart" android:duration="100" android:startOffset="133" android:valueFrom="0.49" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="133" android:startOffset="0" android:valueFrom="0.51" android:valueTo="0.51" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="100" android:startOffset="133" android:valueFrom="0.51" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_T_1"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateXY" android:duration="67" android:startOffset="0" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 180,395C 180,395 180,395 180,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="217" android:startOffset="67" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 180,395C 180,395 432,395 432,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="200" android:startOffset="283" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 432,395C 432,395 420,395 420,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.44,0 0.55,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_T_1"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="10017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="800dp" android:width="800dp" android:viewportHeight="800" android:viewportWidth="800"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="372" android:translateY="396" android:scaleX="9.86" android:scaleY="9.86"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-7.44 23.29 C-8.45,23.29 -9.32,22.93 -10.04,22.21 C-10.76,21.49 -11.13,20.62 -11.13,19.61 C-11.13,19.61 -11.13,8.18 -11.13,8.18 C-11.13,7.71 -10.96,7.31 -10.64,6.99 C-10.31,6.66 -9.91,6.5 -9.45,6.5 C-8.98,6.5 -8.6,6.66 -8.31,6.99 C-7.98,7.31 -7.82,7.71 -7.82,8.18 C-7.82,8.18 -7.82,14.46 -7.82,14.46 C-7.82,14.46 14.82,14.46 14.82,14.46 C14.82,14.46 14.82,-14.46 14.82,-14.46 C14.82,-14.46 -7.82,-14.46 -7.82,-14.46 C-7.82,-14.46 -7.82,-8.18 -7.82,-8.18 C-7.82,-7.71 -7.98,-7.31 -8.31,-6.99 C-8.6,-6.66 -8.98,-6.5 -9.45,-6.5 C-9.91,-6.5 -10.31,-6.66 -10.64,-6.99 C-10.96,-7.31 -11.13,-7.71 -11.13,-8.18 C-11.13,-8.18 -11.13,-19.61 -11.13,-19.61 C-11.13,-20.62 -10.76,-21.49 -10.04,-22.21 C-9.32,-22.93 -8.45,-23.29 -7.44,-23.29 C-7.44,-23.29 14.44,-23.29 14.44,-23.29 C15.45,-23.29 16.32,-22.93 17.04,-22.21 C17.76,-21.49 18.12,-20.62 18.12,-19.61 C18.12,-19.61 18.12,19.61 18.12,19.61 C18.12,20.62 17.76,21.49 17.04,22.21 C16.32,22.93 15.45,23.29 14.44,23.29 C14.44,23.29 -7.44,23.29 -7.44,23.29c  M-7.82 17.77 C-7.82,17.77 -7.82,19.61 -7.82,19.61 C-7.82,19.72 -7.78,19.81 -7.71,19.88 C-7.64,19.95 -7.55,19.99 -7.44,19.99 C-7.44,19.99 14.44,19.99 14.44,19.99 C14.55,19.99 14.64,19.95 14.71,19.88 C14.78,19.81 14.82,19.72 14.82,19.61 C14.82,19.61 14.82,17.77 14.82,17.77 C14.82,17.77 -7.82,17.77 -7.82,17.77c  M-7.82 -17.77 C-7.82,-17.77 14.82,-17.77 14.82,-17.77 C14.82,-17.77 14.82,-19.61 14.82,-19.61 C14.82,-19.72 14.78,-19.81 14.71,-19.88 C14.64,-19.95 14.55,-19.99 14.44,-19.99 C14.44,-19.99 -7.44,-19.99 -7.44,-19.99 C-7.55,-19.99 -7.64,-19.95 -7.71,-19.88 C-7.78,-19.81 -7.82,-19.72 -7.82,-19.61 C-7.82,-19.61 -7.82,-17.77 -7.82,-17.77c "/></group><group android:name="_R_G_L_1_G" android:translateX="213" android:translateY="400" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-28.25 -50.87 C-32.44,-45.82 -32.25,-34.12 -24.87,-27.12 C-14.62,-17.39 -17.69,-20.94 -11.87,-15.25 C-5.87,-9.37 -5.19,-2.07 -11.87,4.5 C-19.5,12 -20.19,12.69 -25.94,18.44 C-31.69,24.19 -31.37,37 -26.37,42 C-21.37,47 -9.56,49.44 -1.75,41.25 C6,33.13 23.88,13.5 29.88,7.5 C37.5,-0.13 38,-10.75 30.13,-19.12 C22.38,-27.37 11.21,-37.59 -1.25,-49.62 C-12.37,-60.37 -23.37,-56.75 -28.25,-50.87c "/></group><group android:name="_R_G_L_0_G" android:translateX="382.403" android:translateY="400" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-169.03 -4.95 C-169.03,3.12 -175.58,9.67 -183.65,9.67 C-183.65,9.67 -188.85,9.67 -188.85,9.67 C-196.93,9.67 -203.48,3.12 -203.48,-4.95 C-203.48,-13.03 -196.93,-19.58 -188.85,-19.58 C-188.85,-19.58 -183.65,-19.58 -183.65,-19.58 C-175.58,-19.58 -169.03,-13.03 -169.03,-4.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="117" android:startOffset="0" android:valueFrom="M-28.25 -50.87 C-32.44,-45.82 -32.25,-34.12 -24.87,-27.12 C-14.62,-17.39 -17.69,-20.94 -11.87,-15.25 C-5.87,-9.37 -5.19,-2.07 -11.87,4.5 C-19.5,12 -20.19,12.69 -25.94,18.44 C-31.69,24.19 -31.37,37 -26.37,42 C-21.37,47 -9.56,49.44 -1.75,41.25 C6,33.13 23.88,13.5 29.88,7.5 C37.5,-0.13 38,-10.75 30.13,-19.12 C22.38,-27.37 11.21,-37.59 -1.25,-49.62 C-12.37,-60.37 -23.37,-56.75 -28.25,-50.87c " android:valueTo="M-28.25 -50.87 C-32.44,-45.82 -32.25,-34.12 -24.87,-27.12 C-14.62,-17.39 -17.69,-20.94 -11.87,-15.25 C-5.87,-9.37 -5.19,-2.07 -11.87,4.5 C-19.5,12 -20.19,12.69 -25.94,18.44 C-31.69,24.19 -31.37,37 -26.37,42 C-21.37,47 -9.56,49.44 -1.75,41.25 C6,33.13 23.88,13.5 29.88,7.5 C37.5,-0.13 38,-10.75 30.13,-19.12 C22.38,-27.37 11.21,-37.59 -1.25,-49.62 C-12.37,-60.37 -23.37,-56.75 -28.25,-50.87c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="117" android:startOffset="117" android:valueFrom="M-28.25 -50.87 C-32.44,-45.82 -32.25,-34.12 -24.87,-27.12 C-14.62,-17.39 -17.69,-20.94 -11.87,-15.25 C-5.87,-9.37 -5.19,-2.07 -11.87,4.5 C-19.5,12 -20.19,12.69 -25.94,18.44 C-31.69,24.19 -31.37,37 -26.37,42 C-21.37,47 -9.56,49.44 -1.75,41.25 C6,33.13 23.88,13.5 29.88,7.5 C37.5,-0.13 38,-10.75 30.13,-19.12 C22.38,-27.37 11.21,-37.59 -1.25,-49.62 C-12.37,-60.37 -23.37,-56.75 -28.25,-50.87c " android:valueTo="M-48.62 -71.62 C-53.69,-65.57 -52.25,-55.12 -44.87,-48.12 C-34.62,-38.39 -17.69,-20.94 -11.87,-15.25 C-5.87,-9.37 -5.19,-2.07 -11.87,4.5 C-19.5,12 -41.69,33.69 -47.44,39.44 C-53.19,45.19 -52.87,58 -47.87,63 C-42.87,68 -31.06,70.44 -23.25,62.25 C-15.5,54.13 23.88,13.5 29.88,7.5 C37.5,-0.13 38,-10.75 30.13,-19.12 C22.38,-27.37 -8.79,-58.59 -21.25,-70.62 C-32.37,-81.37 -43.5,-77.75 -48.62,-71.62c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="67" android:startOffset="0" android:valueFrom="213" android:valueTo="213" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateX" android:duration="267" android:startOffset="67" android:valueFrom="213" android:valueTo="416" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateX" android:duration="333" android:startOffset="333" android:valueFrom="416" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateY" android:duration="67" android:startOffset="0" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateY" android:duration="267" android:startOffset="67" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="333" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="117" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-169.03 -4.95 C-169.03,3.12 -175.58,9.67 -183.65,9.67 C-183.65,9.67 -188.85,9.67 -188.85,9.67 C-196.93,9.67 -203.48,3.12 -203.48,-4.95 C-203.48,-13.03 -196.93,-19.58 -188.85,-19.58 C-188.85,-19.58 -183.65,-19.58 -183.65,-19.58 C-175.58,-19.58 -169.03,-13.03 -169.03,-4.95c " android:valueTo="M-169.03 -4.95 C-169.03,3.12 -175.58,9.67 -183.65,9.67 C-183.65,9.67 -188.85,9.67 -188.85,9.67 C-196.93,9.67 -203.48,3.12 -203.48,-4.95 C-203.48,-13.03 -196.93,-19.58 -188.85,-19.58 C-188.85,-19.58 -183.65,-19.58 -183.65,-19.58 C-175.58,-19.58 -169.03,-13.03 -169.03,-4.95c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-169.03 -4.95 C-169.03,3.12 -175.58,9.67 -183.65,9.67 C-183.65,9.67 -188.85,9.67 -188.85,9.67 C-196.93,9.67 -203.48,3.12 -203.48,-4.95 C-203.48,-13.03 -196.93,-19.58 -188.85,-19.58 C-188.85,-19.58 -183.65,-19.58 -183.65,-19.58 C-175.58,-19.58 -169.03,-13.03 -169.03,-4.95c " android:valueTo="M35.97 -4.95 C35.97,3.12 29.42,9.67 21.35,9.67 C21.35,9.67 -188.85,9.67 -188.85,9.67 C-196.93,9.67 -203.48,3.12 -203.48,-4.95 C-203.48,-13.03 -196.93,-19.58 -188.85,-19.58 C-188.85,-19.58 21.35,-19.58 21.35,-19.58 C29.42,-19.58 35.97,-13.03 35.97,-4.95c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="67" android:startOffset="0" android:valueFrom="382.403" android:valueTo="382.403" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateX" android:duration="267" android:startOffset="67" android:valueFrom="382.403" android:valueTo="398.403" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.3,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateX" android:duration="333" android:startOffset="333" android:valueFrom="398.403" android:valueTo="382.403" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateY" android:duration="67" android:startOffset="0" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateY" android:duration="267" android:startOffset="67" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="333" android:valueFrom="400" android:valueTo="400" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="10017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/res/values-af/strings.xml b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
index 292ca6d..05c0230 100644
--- a/wear/compose/compose-material3/src/main/res/values-af/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Jaar"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestig"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Volgende"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Verminder"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Vermeerder"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Het misluk"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sukses"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Maak op foon oop"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-am/strings.xml b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
index 1f165fd..e9ff4a7 100644
--- a/wear/compose/compose-material3/src/main/res/values-am/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ዓመት"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"አረጋግጥ"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ቀጣይ"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"ቀንስ"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ጨምር"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"አልተሳካም"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ተሳክቷል"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ስልክ ላይ ክፈት"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
index b39dc3c6..53273dd 100644
--- a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
@@ -50,10 +50,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"السنة"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأكيد"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"التالي"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"تقليل"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"زيادة"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"تعذر الإجراء"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"نجحَ الإجراء"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"فتح على الهاتف"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
index 88e265dc..4e4afdb 100644
--- a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalje"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanji"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspelo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspelo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-be/strings.xml b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
index dd3df95..4e4b539 100644
--- a/wear/compose/compose-material3/src/main/res/values-be/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Год"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Пацвердзіць"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далей"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Паменшыць"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Павялічыць"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Памылка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Выканана"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На тэлефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
index df9c2d1..c4df263 100644
--- a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Година"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потвърждаване"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Напред"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Намаляване"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличаване"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Неуспешно"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успешно"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Отв. на тел."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
index 82bc2f2..d963380 100644
--- a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrđivanje"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Naprijed"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanjivanje"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećavanje"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neuspješno"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspješno"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvor. na tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
index 1a58fc7fa..9f2ef3e 100644
--- a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Any"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirma"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Següent"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Redueix"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenta"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ha fallat"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Correcte"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Obre al telèfon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
index 63526fc..2265857 100644
--- a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdit"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Další"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Snížit"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšit"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nezdařilo se"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Hotovo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otevřít v telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-da/strings.xml b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
index fc69382..874dff6 100644
--- a/wear/compose/compose-material3/src/main/res/values-da/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekræft"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Næste"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Dæmp"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øg"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislykket"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Gennemført"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åbn på telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-el/strings.xml b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
index 625ca9a..9f594ae 100644
--- a/wear/compose/compose-material3/src/main/res/values-el/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Έτος"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Επιβεβαίωση"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Επόμενο"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Μείωση"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Αύξηση"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Αποτυχία"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Επιτυχία"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Στο τηλέφωνο"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-es/strings.xml b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
index ec62941..1502faf 100644
--- a/wear/compose/compose-material3/src/main/res/values-es/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Año"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Siguiente"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Reducir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Error"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Todo correcto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ábrelo en el teléfono"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
index 4ed7b8a..e92b365 100644
--- a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"سال"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأیید کردن"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"بعدی"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"کاهش دادن"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"افزایش دادن"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"انجام نشد"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"انجام شد"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"باز کردن در تلفن"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
index 04460eb..6759232 100644
--- a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Vuosi"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Vahvista"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Seuraava"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Vähennä"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Lisää"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Epäonnistui"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Onnistui"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Puhelimella"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
index b3e47a1..a1fbf8e 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Année"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Suivant"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuer"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Réussite"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouv. ds tél."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
index df054a0..7af8ee6 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Année"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Suivant"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuer"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Opération réussie"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouvrir sur le téléphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
index f36d824..d2e3e8e 100644
--- a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalje"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanji"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspjelo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspjeh"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
index fd95148..9fb9621 100644
--- a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Év"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Megerősítés"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Következő"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Csökkentés"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Növelés"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Sikertelen"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sikerült"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Nyissa meg mobilon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
index 7b98efb..453d5f5 100644
--- a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Տարի"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Հաստատել"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Հաջորդը"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Նվազեցնել"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ավելացնել"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ձախողվել է"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Պատրաստ է"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Բացեք հեռախոսում"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-in/strings.xml b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
index b41351a..f4f1f2c 100644
--- a/wear/compose/compose-material3/src/main/res/values-in/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Tahun"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Konfirmasi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Berikutnya"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Turunkan"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tingkatkan"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Gagal"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Berhasil"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buka di ponsel"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-it/strings.xml b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
index 3a0f0b5..5b48a38 100644
--- a/wear/compose/compose-material3/src/main/res/values-it/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Anno"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Conferma"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avanti"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuisci"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumenta"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Non riuscita"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Riuscita"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Su smartph."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
index d0a9318..239c255 100644
--- a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"שנה"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"אישור"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"הבא"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"הפחתה"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"הגברה"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"הפעולה נכשלה"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"הפעולה הצליחה"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"פתיחה בטלפון"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
index 1021184..8cbdd56 100644
--- a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"次へ"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"下げる"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"上げる"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"スマホで開く"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
index ac02463..ed13a53 100644
--- a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"წელი"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"დადასტურება"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"შემდეგი"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"შემცირება"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"გაზრდა"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ვერ შესრულდა"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"შესრულდა"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ტელეფონში გახსნა"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
index 05e8014..4ada781 100644
--- a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Жыл"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Растау"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Келесі"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Азайту"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Көбейту"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Расталмады."</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Расталды."</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефоннан ашыңыз."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
index 1643534..1710e88 100644
--- a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"년"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"확인"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"다음"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"감소"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"증가"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"실패"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"성공"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"휴대전화에서 열기"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
index c312a62..3a9abba 100644
--- a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Жыл"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Ырастоо"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Кийинки"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Төмөндөтүү"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Жогорулатуу"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ишке ашпады"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Ийгилик"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефондо ачуу"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
index 8397c2a..474becb 100644
--- a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Metai"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Patvirtinti"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Kitas"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Sumažinti"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Padidinti"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nepavyko"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pavyko"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atidaryti telefone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
index c440c8b..fc3777f 100644
--- a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Gads"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Apstiprināt"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Tālāk"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Samazināt"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Palielināt"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neizdevās"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Izdevās"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atvērt tālrunī"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-my/strings.xml b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
index c0393c2..1c2fc6e1 100644
--- a/wear/compose/compose-material3/src/main/res/values-my/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"နှစ်"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"အတည်ပြုရန်"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ရှေ့သို့"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"လျှော့ရန်"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"တိုးရန်"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"မအောင်မြင်ပါ"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"အောင်မြင်သည်"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ဖုန်း၌ဖွင့်ရန်"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
index d827ac5..6e3d0e6 100644
--- a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekreft"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Neste"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Reduser"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øk"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislyktes"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Vellykket"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åpne på tlf."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
index d89b6e9..a72911c 100644
--- a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Jaar"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestigen"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Volgende"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Verlagen"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Verhogen"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislukt"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Geslaagd"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Openen op telefoon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
index 5484b3e..b958497 100644
--- a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potwierdź"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalej"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Zmniejsz"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zwiększ"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Niepowodzenie"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Udało się"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otwórz na telefonie"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
index 12c2766..41b3ee7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avançar"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
index c4d5152..3370f8b 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Seguinte"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falhou"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Concluído"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abrir no tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
index 12c2766..41b3ee7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avançar"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
index 03c63d69..10fbc69 100644
--- a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"An"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmă"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Înainte"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Redu"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Crește"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Eroare"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Succes"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Pe telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
index 84d7feb..2cffbc8 100644
--- a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Год"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Подтвердить"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далее"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Уменьшить"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличить"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ошибка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Открыть на телефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
index 0b391a8..4922721 100644
--- a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdiť"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Ďalej"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Znížiť"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšiť"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neúspešné"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Podarilo sa"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvorte v telefóne"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
index c5bdeb3..07713ae 100644
--- a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Година"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потврди"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Даље"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Смањи"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Повећај"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Није успело"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успело"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефону"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
index 8e24d46..a4c4e6a 100644
--- a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekräfta"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Nästa"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Minska"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Öka"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Misslyckades"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Klart"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"På telefonen"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
index c2ad8cb..942c959 100644
--- a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Mwaka"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Thibitisha"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Endelea"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Punguza"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ongeza"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Imeshindwa"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Imemaliza"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Fungua kwenye simu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
index 4f93096..fd278fc 100644
--- a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ஆண்டு"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"உறுதிசெய்யும்"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"அடுத்ததற்குச் செல்லும்"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"குறைக்கும்"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"அதிகரிக்கும்"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"தோல்வி"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"முடிந்தது"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"மொபைலில் திற"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-th/strings.xml b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
index d4825e4..8576aa4 100644
--- a/wear/compose/compose-material3/src/main/res/values-th/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ปี"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ยืนยัน"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ถัดไป"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"ลด"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"เพิ่ม"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ไม่สำเร็จ"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"สำเร็จ"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"เปิดในโทรศัพท์"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
index 186c0ed..f8cf9bf7a 100644
--- a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Taon"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Kumpirmahin"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Susunod"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Bawasan"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Dagdagan"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nabigo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Matagumpay"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buksan"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
index c978fe0..a8a32e6 100644
--- a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Yıl"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Onayla"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Sonraki"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Azalt"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Artır"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Başarısız"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Başarılı"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda aç"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
index 3fa6994..0c45993 100644
--- a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Рік"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Підтвердити"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далі"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Зменшити"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Збільшити"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Помилка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефоні"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
index d99a721..2d2f2d3 100644
--- a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Yil"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Tasdiqlash"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Keyingisi"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Pasaytirish"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Oshirish"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Bajarilmadi"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Bajarildi"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
index 65f5eb1..093ccf7 100644
--- a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Năm"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Xác nhận"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Tiếp theo"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Giảm"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tăng"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Lỗi"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Thành công"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Mở trên điện thoại"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
index c395e20..81d4b75 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"确认"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一个"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"降低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"增加"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失败"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手机上打开"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
index 7306379..64d4bdb 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一步"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"調低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
index dfe78d7..ef57e4c 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一個"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"調低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機上開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
index 259ef34..c14db45 100644
--- a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Unyaka"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Qinisekisa"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Okulandelayo"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Yehlisa"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Khulisa"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Yehlulekile"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Impumelelo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Vula efonini"</string>
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
index fc26ae4..7d53524 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
@@ -90,11 +90,11 @@
                 val coroutineScope = rememberCoroutineScope()
                 val deleteItem: () -> Unit = {
                     coroutineScope.launch {
-                        revealState.animateTo(RevealValue.Revealed)
+                        revealState.animateTo(RevealValue.RightRevealed)
 
                         // hide the content after some time if the state is still revealed
                         delay(1500)
-                        if (revealState.currentValue == RevealValue.Revealed) {
+                        if (revealState.currentValue == RevealValue.RightRevealed) {
                             // Undo should no longer be triggered
                             undoActionEnabled = false
                             currentState.expanded = false
@@ -103,12 +103,12 @@
                 }
                 val addItem: () -> Unit = {
                     coroutineScope.launch {
-                        revealState.animateTo(RevealValue.Revealed)
+                        revealState.animateTo(RevealValue.RightRevealed)
                         itemCount++
 
                         // reset the state after some delay if the state is still revealed
                         delay(2000)
-                        if (revealState.currentValue == RevealValue.Revealed) {
+                        if (revealState.currentValue == RevealValue.RightRevealed) {
                             revealState.animateTo(RevealValue.Covered)
                         }
                     }
@@ -268,7 +268,7 @@
     val coroutineScope = rememberCoroutineScope()
     var showDialog by remember { mutableStateOf(false) }
     LaunchedEffect(revealState.currentValue) {
-        if (revealState.currentValue == RevealValue.Revealed) {
+        if (revealState.currentValue == RevealValue.RightRevealed) {
             delay(2000)
             expandableState.expanded = false
         }
@@ -290,7 +290,9 @@
                 customActions =
                     listOf(
                         CustomAccessibilityAction("Delete") {
-                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.RightRevealed)
+                            }
                             true
                         },
                         CustomAccessibilityAction("More Options") {
@@ -300,13 +302,17 @@
                     )
             },
         revealState = revealState,
-        onFullSwipe = { coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) } },
+        onFullSwipe = {
+            coroutineScope.launch { revealState.animateTo(RevealValue.RightRevealed) }
+        },
         primaryAction = {
             SwipeToRevealPrimaryAction(
                 revealState = revealState,
                 icon = { Icon(SwipeToRevealDefaults.Delete, contentDescription = "Delete") },
                 label = { Text(text = "Delete") },
-                onClick = { coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) } }
+                onClick = {
+                    coroutineScope.launch { revealState.animateTo(RevealValue.RightRevealed) }
+                }
             )
         },
         secondaryAction = {